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)
}
SnippetFires forvalue type
stringString leavesstring
numberNumber leavesnumber
booleanBoolean leavesboolean
nullnull leavesnull
undefinedundefined leavesundefined
bigintbigint leavesbigint
dateDate instancesDate
functionFunction valuesFunction
labelEvery 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

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.

SnippetReceivesResponsibility
rowid, field, value, level, isContainer, expanded, attrs, childrenSpread attrs on the row root; render children() once.
expanderfield, value, level, expanded, count, isArray, hovered, focused, focusVisibleRender noninteractive content inside the viewer’s accessible button. Replaces the default glyph.
collapsedfield, value, level, expanded, count, isArrayRender a persistent indicator before the closing bracket; animate it to zero width when expanded. The viewer handles clicks when collapsed unless clickToExpandSummary={false}.
childGroupContainer state plus attrs, childrenSpread 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.