JSON viewer
Collapsible, syntax-colored JSON tree.
$
pnpm dlx shadcn@latest add https://lodeui.com/r/json-viewer.json"id": "evt_1Q8x",
"type": "payment.succeeded",
"livemode": false,
"amount": 4900,
"currency": "usd",
3 keys }
},
2 keys }
],
"metadata": null
}
components/examples/json-viewer-demo.tsx
"use client";
import { JsonViewer } from "@/components/ui/json-viewer";
export default function JsonViewerDemo() {
return (
<JsonViewer
className="w-full max-w-lg"
data={{
id: "evt_1Q8x",
type: "payment.succeeded",
livemode: false,
data: { amount: 4900, currency: "usd", customer: { id: "cus_91", email: "billing@acme.com", tags: ["vip", "annual"] } },
attempts: [{ at: "2026-10-07T10:42:18Z", status: 200 }],
metadata: null,
}}
/>
);
}When to use
- Inspecting API responses, webhook payloads and config.
When not to use
- Editing JSON: use a code editor.
Installation
$
pnpm dlx shadcn@latest add https://lodeui.com/r/json-viewer.json1Install the dependencies
npm install lucide-react2Copy the source into your project
components/ui/json-viewer.tsx"use client"; import * as React from "react"; import { ChevronRight } from "lucide-react"; import { cn } from "@/lib/utils"; type Json = string | number | boolean | null | Json[] | { [k: string]: Json }; export interface JsonViewerProps { data: Json; /** Levels expanded on first render. */ defaultExpandDepth?: number; rootName?: string; className?: string; } function Value({ v }: { v: Json }) { if (v === null) return <span className="c-key">null</span>; if (typeof v === "string") return <span className="c-str">"{v}"</span>; if (typeof v === "number") return <span className="c-num">{v}</span>; if (typeof v === "boolean") return <span className="c-key">{String(v)}</span>; return null; } function Node({ name, value, depth, expandDepth, last }: { name?: string; value: Json; depth: number; expandDepth: number; last: boolean }) { const [open, setOpen] = React.useState(depth < expandDepth); const isObj = value !== null && typeof value === "object"; const entries = isObj ? (Array.isArray(value) ? value.map((v, i) => [String(i), v] as const) : Object.entries(value)) : []; const [openB, closeB] = Array.isArray(value) ? ["[", "]"] : ["{", "}"]; const label = name !== undefined && <><span className="c-prop">{Array.isArray(value) || !/^\d+$/.test(name) ? `"${name}"` : name}</span>: </>; if (!isObj) return ( <div className="pl-5"> {label} <Value v={value} /> {!last && ","} </div> ); return ( <div className={cn(depth > 0 && "pl-5")}> <button type="button" aria-expanded={open} onClick={() => setOpen((o) => !o)} className="-ml-5 inline-flex items-center rounded hover:bg-accent/60"> <ChevronRight className={cn("size-4 text-muted-foreground transition-transform", open && "rotate-90")} /> <span>{label}{openB}</span> </button> {open ? ( <> {entries.map(([k, v], i) => ( <Node key={k} name={Array.isArray(value) ? undefined : k} value={v} depth={depth + 1} expandDepth={expandDepth} last={i === entries.length - 1} /> ))} <div>{closeB}{!last && ","}</div> </> ) : ( <span className="text-muted-foreground"> {entries.length} {Array.isArray(value) ? "items" : "keys"} {closeB}{!last && ","}</span> )} </div> ); } /** Collapsible JSON tree. */ export function JsonViewer({ data, defaultExpandDepth = 2, className }: JsonViewerProps) { return ( <div className={cn("lode-code overflow-x-auto rounded-lg border border-border bg-card p-3 pl-7 font-mono text-[12.5px] leading-6", className)} role="tree"> <Node value={data} depth={0} expandDepth={defaultExpandDepth} last /> </div> ); }
Usage
Import it from @/components/ui/json-viewer after installing.
example.tsx
import { JsonViewer } from "@/components/ui/json-viewer";
export function Payload({ event }: { event: unknown }) {
return <JsonViewer data={event as never} defaultExpandDepth={1} />;
}API reference
JsonViewer
A collapsible JSON tree with type colors and item counts.
| Prop | Type | Default | Description |
|---|---|---|---|
| data | Json | – | Any JSON value. |
| defaultExpandDepth | number | 2 | Levels open on first render. |
| rootName | string | – | Label for the root. |
Keyboard interactions
| Keys | Action |
|---|---|
| Tab | Move between expandable nodes |
| EnterorSpace | Expand or collapse |
Accessibility
- role="tree"; toggles use aria-expanded.
Motion
- Chevrons rotate as nodes open.
Responsive behavior
- Scrolls horizontally for deep nesting.
Performance
- Collapsed nodes don't render their children.
Notes for AI
- Lower defaultExpandDepth for large payloads.
This page as Markdown, for assistants and coding agents:
/components/json-viewer/markdownThe full library index for assistants is at /llms.txt.
Lode222 doc pagesllms.txt