Skip to content
omaris

Data Display

JsonViewer

A JSON tree you can actually read: collapsible, searchable, and coloured with the same --code-* tokens as CodeBlock, so a payload in a docs page and a payload in a debug panel look like the same language.

import { JsonViewer } from 'omaris'
Learn

Examples

Basic

Give it any value. depth decides how many levels start open — a collapsed node still says what is inside it rather than showing an opaque {…}.

$ : { 3 keys
ok : true
order : { 5 keys
id : "ord_8f21"
total : 24500
currency : "IQD"
customer : { name, phone, repeat } 3 keys
items : [ {…}, {…} ] 2 items
}
delivered_at : null
}
<script lang="ts">	import { JsonViewer } from 'omaris';​	const RESPONSE = {		ok: true,		order: {			id: 'ord_8f21',			total: 24500,			currency: 'IQD',			customer: { name: 'Lina Aziz', phone: '+964 750 000 0000', repeat: true },			items: [				{ sku: 'lamb-quzi', qty: 1, price: 15000 },				{ sku: 'tea', qty: 2, price: 1500 }			]		},		delivered_at: null	};</script>​<JsonViewer value={RESPONSE} depth={2} class="w-full" />

Search and variants

search filters the tree and keeps a match's ancestors, so a hit five levels down arrives with the path that leads to it. Try warn. The three variants are the same ones CodeBlock has.

config : { 5 keys
service : "checkout"
region : "eu-west-1"
logging : { level, sinks, sample } 3 keys
limits : { rps, burst, timeout_ms } 3 keys
flags : { warn_on_retry, new_pricing } 2 keys
}
<script lang="ts">	import { JsonViewer, SegmentedButton, type JsonViewerVariant } from 'omaris';​	const CONFIG = {		service: 'checkout',		region: 'eu-west-1',		logging: { level: 'warn', sinks: ['stdout', 'file'], sample: 0.25 },		limits: { rps: 200, burst: 400, timeout_ms: 3000 },		flags: { warn_on_retry: true, new_pricing: false }	};​	let variant = $state<JsonViewerVariant>('outlined');</script>​<div class="flex w-full flex-col gap-3">	<SegmentedButton		label="Variant"		size="sm"		value={variant}		onchange={(next) => (variant = next as JsonViewerVariant)}		items={[			{ value: 'outlined', label: 'Outlined' },			{ value: 'filled', label: 'Filled' },			{ value: 'ghost', label: 'Ghost' }		]}	/>​	<JsonViewer value={CONFIG} name="config" depth={1} search {variant} height="14rem" /></div>

Overridden

height is a custom property, not a class, so height="auto" lets the tree grow with its content and a max-h-* on classes.viewport really replaces it. indent tightens the levels and copy={false} drops the buttons.

event : { 4 keys
type : "deploy.finished"
build : 4821
duration_s : 96.4
commit : { 3 keys
sha : "dafc5ae"
author : "omer"
message : "Ship the docs demos"
}
}
<script lang="ts">	import { JsonViewer } from 'omaris';​	const EVENT = {		type: 'deploy.finished',		build: 4821,		duration_s: 96.4,		commit: { sha: 'dafc5ae', author: 'omer', message: 'Ship the docs demos' }	};</script>​<JsonViewer	value={EVENT}	name="event"	depth={Infinity}	copy={false}	indent={22}	height="auto"	size="sm"	variant="ghost"	classes={{		root: 'rounded-shape-lg bg-surface-container-low',		viewport: 'max-h-none py-2',		key: 'font-semibold'	}}	class="w-full"/>

When to use it

Use it for

  • A payload someone will explore: an API response in a debug panel, a webhook's details, a row's raw record. depth={2} opens the top, and a collapsed node still says what is inside: { id, name, +3 }, [ 12 items ].
  • Finding one field in a large document. search keeps the matches and the path down to them, so a hit five levels deep arrives with its ancestors.
  • Grabbing a piece. copy puts a copy button on every node and a copy-all in the header.
  • A string that might be JSON. It is parsed when it is, and shown as a string when it is not.

Not for

  • A short, fixed example nobody expands → Code Block with lang="json". It copies and highlights with the same tokens.
  • Editing the document → Code Editor with lang="json".
  • Structured log lines → Log Viewer. A row's details object already opens as this tree.
  • Data with a fixed shape people compare across rows → Table.

Do

  • Name the root with name, like response or payload, so the first row is not a bare brace.
  • Start shallow: depth={1} for a document you do not know, Infinity only for something small. The previews make skipping a node possible.
  • Give it a height when it sits in a panel and let the tree scroll inside. Use height="auto" when the card around it scrolls.
  • Use variant="ghost" inside a bordered surface. The three variants match Code Block.

Don't

  • Stringify first. Pass the object; a pre-stringified value loses the types, and the colours are the types.
  • Turn search on for a ten-key object. The field is taller than the tree.
  • Set the height from a class alone. height is a property, so a max-h-* on classes.viewport only works once height="auto" has cleared it.

Quick reference

variant
  • outlined (default)
  • filled
  • ghost
size
  • sm
  • md (default)

API

JsonViewer

A JSON tree you can actually read: collapsible, searchable, and coloured with the same --code-* tokens as CodeBlock, so a payload in a docs page and a payload in a debug panel look like the same language.

A collapsed node still tells you what is inside it — { id, name, +3 }, [ 12 items ] — because the usual alternative, an opaque {…}, means expanding every node just to find the one you wanted.

Filtering keeps matches and their ancestors, so a hit five levels down arrives with the path that leads to it rather than as an orphan row.

import { JsonViewer } from 'omaris'
<JsonViewer value={response} depth={2} search copy />

Parts

Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.

root
No description in the source yet.
header
Filter box and the toolbar buttons.
search
No description in the source yet.
action
No description in the source yet.
viewport
The scrolling tree.
row
One row: the toggle, the key, and either a value or a brace.
toggle
The disclosure triangle. A leaf gets an empty one, to keep the column.
spacer
No description in the source yet.
key
No description in the source yet.
punctuation
No description in the source yet.
value
A scalar. The colour says which type it is.
preview
The summary drawn inside a collapsed brace.
count
12 items, at the end of a container row.
copy
Copy-this-node, revealed on hover.
empty
Nothing matched the filter.

Props

value required
unknown

Any value. A string that parses as JSON is parsed; one that doesn't is shown as a string.

name

Defaults to '$'

string

Name for the root node.

variant

Defaults to 'outlined'

JsonViewerVariant
outlined
filled
ghost
size

Defaults to 'md'

JsonViewerSize
sm
md
depth

Defaults to 1

number

How many levels start open. Infinity opens everything.

search

Defaults to false

boolean

Show the filter box.

copy

Defaults to true

boolean

Per-node copy buttons, and a copy-all in the header.

maxStringLength

Defaults to 120

number

Longest string shown before it is elided.

height

Defaults to '20rem'

number | string

Height of the scroll area. A number is px, a string any CSS length — a property rather than a class, so classes.viewport can replace it.

indent

Defaults to 14

number

Indent per level, in px.

class
string
classes
JsonViewerClasses