JSON viewer
A collapsible JSON tree with search, paging for long arrays, and copy value or path.
pnpm dlx shadcn@latest add @uiarc/json-viewerLive · keyboard ready
GET
/v1/orders/ord_8Kd2Q720084 msorder13 keys
id"ord_8Kd2Q7"
object"order"
status"fulfilled"
livemodefalse
created1757851200
currency"usd"
amount_total48600
customer{ id, name, email, … }5 keys
items[…]3 items
shipping{ carrier, service, tracking, … }4 keys
discountnull
metadata{ campaign id, gift wrap }2 keys
events[…]180 items
orderobject, 13 keys- Inspecting a JSON payload in a developer dashboard, such as webhooks or API logs.
- Large nested objects where search and copy path save time.
- Arrays with hundreds of items, which load in pages of pageSize.
- Use code-block to show JSON as formatted source for copying whole.
- Use tree-view for folders, navigation, or other non-JSON hierarchies.
- Use data-grid when the data is a flat list of records.
Installation
Add JSON viewer with the shadcn CLI, or copy the source by hand.
pnpm dlx shadcn@latest add @uiarc/json-viewerAdds the component and its local dependencies, and installs motion, lucide-react. First time? Add the @uiarc registry to components.json, or use the full URL:
example.tsx
import { JsonViewer } from "@/components/arc/json-viewer/json-viewer"; export function WebhookPayload({ payload }: { payload: unknown }) { return ( <JsonViewer data={payload} rootName="event" defaultExpandDepth={2} maxHeight={360} onCopy={({ kind }) => toast(kind === "path" ? "Path copied" : "Value copied")} /> );}JsonViewer
A collapsible JSON tree with type colors, search that opens matching branches, paged long branches, and copy value or path per row.
PropTypeDefaultDescription
dataRequiredunknown–Any JSON-like value.rootNamestring"root"Name of the root in paths, as in root.users[0].name.defaultExpandDepthnumber1Levels open on first render when defaultExpanded is not set. 1 opens the root.expandedstring[]–Controlled paths of open branches.defaultExpandedstring[]–Open branch paths on first render when uncontrolled.onExpandedChange(paths: string[]) => void–Called when branches open or close.searchablebooleantrueShows the search field.querystring–Controlled search text.defaultQuerystring""Starting search text when uncontrolled.onQueryChange(query: string) => void–Called as the search text changes.pageSizenumber50Children shown per page in a long array or object.copyablebooleantrueShows copy value and copy path actions.onCopy(detail: JsonViewerCopyDetail) => void–Called after a copy with { kind: "value" | "path", path, text }.onSelect(detail: { path: string; value: unknown; type: JsonValueType }) => void–Called when a row becomes the current one.showPathbooleantrueShows the current row's path under the tree.maxHeightnumber | string420Height of the scrolling tree area. Numbers are pixels.labelstring"JSON"Accessible name of the tree.classNamestring–Class on the root.refRef<HTMLDivElement>–The root element.- ArrowDownorArrowUp
- Moves to the next or previous visible row.
- ArrowRight
- Opens a closed branch, or moves into an open one.
- ArrowLeft
- Closes an open branch, or moves to the parent.
- HomeorEnd
- Jumps to the first or last visible row.
- EnterorSpace
- Toggles a branch, or loads the next page on a Show more row.
- Cmd/Ctrl+C
- Copies the focused row's value; add Shift to copy its path. Ignored while text is selected.
- /
- Focuses the search field.
- EnterorShift+Enter (in search)
- Jumps to the next or previous match.
- Escape (in search)
- Clears the query.
- ArrowDown (in search)
- Moves focus into the tree.
- Follows the WAI-ARIA tree pattern: role="tree" with treeitems carrying aria-level, aria-posinset, aria-setsize, aria-expanded, and aria-selected.
- One row is tabbable at a time; each row's label reads its key and value, or its type and item count.
- The match counter is aria-live polite, and a status region announces copies and failures.
- Row copy buttons are out of the tab order; use Cmd or Ctrl with C, or the path bar's copy button, from the keyboard.
- Each row unfolds its own height, so opening a branch, loading a page, or expanding all share one motion.
- The current-row highlight glides between rows with a shared layout id, and the tree scroll springs to where the row will sit once rows settle.
- Copy glyphs swap to a check or cross with a small scale and blur.
- Reduced motion, or more than 400 visible rows, makes row changes instant; reduced motion also jumps the scroll and highlight.
- The tree scrolls inside maxHeight; long keys and values truncate with an ellipsis and strings over 40 characters show in full on hover.
- At 420px viewport width and below the indent drops from 16px to 12px, keys cap at 40% of the row, and the path type hides.
- Row copy actions show on hover with a fine pointer; on touch they show on the current row.
- Rows are a flat list rebuilt only when data, open branches, pages, or search change; nothing is virtualized, so paging keeps the DOM small.
- Above 400 visible rows the height animations switch off to keep expand all fast.
- Search walks the whole value on each keystroke; for multi-megabyte payloads control query and debounce it.
Notes for AI
Give your coding assistant the Markdown reference instead of screenshots.
- Use for API responses, webhook payloads, logs, and config inspection in developer tools.
- Search matches keys and primitive values case-insensitively, opens the branches that hold them, and stops at 2000 matches.
- Control expanded to persist open branches across reloads, or to open a path from elsewhere in the page.
- For formatted source code use code-block; for tabular data use data-grid.
The full library index for assistants is at /llms.txt.