Snippet Overrides
Snippet overrides are the one deliberate extension the Svelte port adds on top of react-json-view-lite’s API. Pass a value or label snippet to replace its default renderer, or use structural snippets to compose your own row and child-group components.
<script lang="ts">
import { JsonView } from '@humanspeak/svelte-json-view-lite'
</script>
<JsonView data={payload}>
{#snippet string({ value })}
<span class="text-brand-500">{value}</span>
{/snippet}
</JsonView><script lang="ts">
import { JsonView } from '@humanspeak/svelte-json-view-lite'
</script>
<JsonView data={payload}>
{#snippet string({ value })}
<span class="text-brand-500">{value}</span>
{/snippet}
</JsonView>Snippet Props
Every value snippet receives the same shape — just with a differently typed value:
interface ValueSnippetProps<T> {
value: T
field?: string // parent key, or undefined at the root / inside arrays
level: number // depth (root = 0)
}interface ValueSnippetProps<T> {
value: T
field?: string // parent key, or undefined at the root / inside arrays
level: number // depth (root = 0)
}| Snippet | Fires for | value type |
|---|---|---|
string | String leaves | string |
number | Number leaves | number |
boolean | Boolean leaves | boolean |
null | null leaves | null |
undefined | undefined leaves | undefined |
bigint | bigint leaves | bigint |
date | Date instances | Date |
function | Function values | Function |
label | Every field label (key) | — ({ field, level }) |
See Types & snippets for the exported type aliases.
Render URLs as links
<script lang="ts">
import { JsonView } from '@humanspeak/svelte-json-view-lite'
const payload = {
repo: 'https://github.com/humanspeak/svelte-json-view-lite',
email: 'hello@humanspeak.com'
}
const isUrl = (s: string) => /^https?:\/\//.test(s)
</script>
<JsonView data={payload}>
{#snippet string({ value })}
{#if isUrl(value)}
<a href={value} target="_blank" rel="noopener" class="text-brand-500 underline">
{value}
</a>
{:else}
"{value}"
{/if}
{/snippet}
</JsonView><script lang="ts">
import { JsonView } from '@humanspeak/svelte-json-view-lite'
const payload = {
repo: 'https://github.com/humanspeak/svelte-json-view-lite',
email: 'hello@humanspeak.com'
}
const isUrl = (s: string) => /^https?:\/\//.test(s)
</script>
<JsonView data={payload}>
{#snippet string({ value })}
{#if isUrl(value)}
<a href={value} target="_blank" rel="noopener" class="text-brand-500 underline">
{value}
</a>
{:else}
"{value}"
{/if}
{/snippet}
</JsonView>Relative time for Date values
<script lang="ts">
import { JsonView } from '@humanspeak/svelte-json-view-lite'
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })
const relative = (d: Date) => {
const diff = d.getTime() - Date.now()
const days = Math.round(diff / 86400000)
return rtf.format(days, 'day')
}
</script>
<JsonView data={{ lastSeen: new Date(Date.now() - 3 * 86400000) }}>
{#snippet date({ value })}
<time datetime={value.toISOString()} title={value.toISOString()}>
{relative(value)}
</time>
{/snippet}
</JsonView><script lang="ts">
import { JsonView } from '@humanspeak/svelte-json-view-lite'
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })
const relative = (d: Date) => {
const diff = d.getTime() - Date.now()
const days = Math.round(diff / 86400000)
return rtf.format(days, 'day')
}
</script>
<JsonView data={{ lastSeen: new Date(Date.now() - 3 * 86400000) }}>
{#snippet date({ value })}
<time datetime={value.toISOString()} title={value.toISOString()}>
{relative(value)}
</time>
{/snippet}
</JsonView>Format large numbers
<script lang="ts">
import { JsonView } from '@humanspeak/svelte-json-view-lite'
const fmt = new Intl.NumberFormat('en-US')
</script>
<JsonView data={{ views: 12_345_678, errors: 0 }}>
{#snippet number({ value })}
<span class="tabular-nums">{fmt.format(value)}</span>
{/snippet}
</JsonView><script lang="ts">
import { JsonView } from '@humanspeak/svelte-json-view-lite'
const fmt = new Intl.NumberFormat('en-US')
</script>
<JsonView data={{ views: 12_345_678, errors: 0 }}>
{#snippet number({ value })}
<span class="tabular-nums">{fmt.format(value)}</span>
{/snippet}
</JsonView>Badge booleans
<JsonView data={{ active: true, verified: false }}>
{#snippet boolean({ value })}
<span
class="rounded px-1.5 py-0.5 text-xs font-medium"
class:bg-green-100={value}
class:text-green-900={value}
class:bg-red-100={!value}
class:text-red-900={!value}
>
{value ? 'yes' : 'no'}
</span>
{/snippet}
</JsonView><JsonView data={{ active: true, verified: false }}>
{#snippet boolean({ value })}
<span
class="rounded px-1.5 py-0.5 text-xs font-medium"
class:bg-green-100={value}
class:text-green-900={value}
class:bg-red-100={!value}
class:text-red-900={!value}
>
{value ? 'yes' : 'no'}
</span>
{/snippet}
</JsonView>Field-scoped rendering
The field prop lets you key behavior off the parent label:
<JsonView data={{ email: 'ada@example.com', bio: 'Mathematician.' }}>
{#snippet string({ value, field })}
{#if field === 'email'}
<a href={`mailto:${value}`}>{value}</a>
{:else}
"{value}"
{/if}
{/snippet}
</JsonView><JsonView data={{ email: 'ada@example.com', bio: 'Mathematician.' }}>
{#snippet string({ value, field })}
{#if field === 'email'}
<a href={`mailto:${value}`}>{value}</a>
{:else}
"{value}"
{/if}
{/snippet}
</JsonView>field is undefined at the root and inside array elements. level is the depth — useful for nested-specific styling.
Custom label renderer
The label snippet replaces the default key rendering. It receives { field, level } and no value:
<JsonView data={payload}>
{#snippet label({ field, level })}
<span class="font-mono text-muted-foreground" class:text-brand-500={level === 0}>
{field}
</span>
{/snippet}
</JsonView><JsonView data={payload}>
{#snippet label({ field, level })}
<span class="font-mono text-muted-foreground" class:text-brand-500={level === 0}>
{field}
</span>
{/snippet}
</JsonView>Mixing themes and snippets
Snippets only replace the value cell. The surrounding container, punctuation, and field label continue to use StyleProps classes and --sjv-* tokens. Combine both for full theming:
<JsonView data={payload} style={darkStyles}>
{#snippet string({ value })}
<em>{value}</em>
{/snippet}
</JsonView><JsonView data={payload} style={darkStyles}>
{#snippet string({ value })}
<em>{value}</em>
{/snippet}
</JsonView>Related
- JsonView props — every snippet listed alongside the other props
- Types & snippets — exported prop types for each snippet
- Themes & CSS variables — theming the parts snippets don’t touch
Structural snippets
Use row, expander, collapsed, and childGroup to customize the tree’s structure.
The viewer owns expansion, veto callbacks, keyboard navigation, and focus. Your snippets own
presentation, including declarative Svelte Motion components.
| Snippet | Receives | Responsibility |
|---|---|---|
row | id, field, value, level, isContainer, expanded, attrs, children | Spread attrs on the row root; render children() once. |
expander | field, value, level, expanded, count, isArray, hovered, focused, focusVisible | Render noninteractive content inside the viewer’s accessible button. Replaces the default glyph. |
collapsed | field, value, level, expanded, count, isArray | Render a persistent indicator before the closing bracket; animate it to zero width when expanded. The viewer handles clicks when collapsed unless clickToExpandSummary={false}. |
childGroup | Container state plus attrs, children | Spread attrs on the group and render children() inside it. Own conditional rendering and any exit animation. |
<script lang="ts">
import { JsonView } from '@humanspeak/svelte-json-view-lite'
import { AnimatePresence, MotionUl, useReducedMotion } from '@humanspeak/svelte-motion'
const reducedMotion = useReducedMotion()
</script>
<JsonView {data}>
{#snippet childGroup({ expanded, attrs, children })}
<AnimatePresence present={expanded} initial={false}>
{#snippet child()}
<MotionUl
{...attrs}
initial={{ height: 0 }}
animate={{ height: 'auto' }}
exit={{ height: 0 }}
transition={{ duration: reducedMotion.current ? 0 : 0.24 }}
style={{ overflow: 'hidden' }}
>
{@render children()}
</MotionUl>
{/snippet}
</AnimatePresence>
{/snippet}
</JsonView><script lang="ts">
import { JsonView } from '@humanspeak/svelte-json-view-lite'
import { AnimatePresence, MotionUl, useReducedMotion } from '@humanspeak/svelte-motion'
const reducedMotion = useReducedMotion()
</script>
<JsonView {data}>
{#snippet childGroup({ expanded, attrs, children })}
<AnimatePresence present={expanded} initial={false}>
{#snippet child()}
<MotionUl
{...attrs}
initial={{ height: 0 }}
animate={{ height: 'auto' }}
exit={{ height: 0 }}
transition={{ duration: reducedMotion.current ? 0 : 0.24 }}
style={{ overflow: 'hidden' }}
>
{@render children()}
</MotionUl>
{/snippet}
</AnimatePresence>
{/snippet}
</JsonView>childGroup stays mounted as expansion changes, so it can retain real child nodes during exits.
Its attrs preserve the group ID, ARIA role, hidden state, and inert state. Child values stay
lazy until first expansion. A supplied childGroup owns animation instead of childrenTransition. Existing value and label snippets compose with all four structural snippets.
See the Tailwind + Svelte Motion example for all four together. Svelte Motion is an optional application dependency.
Copyable rows
Set clickToExpandSummary={false} to keep the default summary or custom collapsed punctuation
from expanding on click. The default is true, preserving existing behavior. Keep clickToExpandNode at its default false when labels should copy too.
<JsonView {data} clickToExpandSummary={false} style={{ hideCommas: true }}>
<!-- Supply your row, expander, collapsed, and childGroup snippets here. -->
</JsonView><JsonView {data} clickToExpandSummary={false} style={{ hideCommas: true }}>
<!-- Supply your row, expander, collapsed, and childGroup snippets here. -->
</JsonView>A row snippet can use an ordinary bubbling onclick handler. Check that the clicked node
belongs to the current row, exclude chevron buttons, and preserve selected text. Chevron clicks
and arrow-key navigation remain available. If rows also support keyboard copying, provide
focus styling and accessible instructions, as in the Motion + Tailwind example.
style.hideCommas removes separators from all library-rendered rows, including collapsed,
expanded, and empty containers. It defaults to false. Brackets, commas inside strings,
and data are preserved; punctuation emitted by your own snippets remains under your control.
Collapsed snippet spacing
The viewer renders custom collapsed snippets inside its own inline-flex span. Whitespace
emitted around snippet elements cannot add a gap before the closing bracket, including when
you override the punctuation class. Intentional spacing inside snippet elements is preserved.
A persistent animated indicator should animate its width and margins to zero when expanded.
Keyboard-visible caret focus
Use focusVisible for a keyboard-only caret tilt. focused tracks native focus and can stay true after a mouse click, so animating from it alone can leave a caret tilted.
<JsonView {data}>
{#snippet expander({ expanded, focusVisible })}
<span
class="caret"
class:keyboard-focused={focusVisible}
aria-hidden="true"
>{expanded ? '▾' : '▸'}</span>
{/snippet}
</JsonView><JsonView {data}>
{#snippet expander({ expanded, focusVisible })}
<span
class="caret"
class:keyboard-focused={focusVisible}
aria-hidden="true"
>{expanded ? '▾' : '▸'}</span>
{/snippet}
</JsonView>Keep expander snippet content noninteractive; the viewer owns its button, focus, and keyboard handlers. See expander state types for all three flags.