JSON Tree Viewer
A collapsible JSON tree with type-based coloring, path copying, and an expand animation.
Recursively renders any JSON data in a monospaced font: strings green, numbers blue, booleans orange, null red. Objects and arrays expand and collapse (a height animation plus a rotating arrow, with a summary of the item count), and hovering any row lets you copy that node's path or value in one click — a good fit for API debugging panels and developer tools.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add json-tree-viewer.enJSON Tree Viewer is a React JSON viewer component built with Tailwind CSS and Motion, with icons from lucide-react. Pass it a JsonValue and it renders a collapsible tree in which every row can copy that node's access path (such as $.user.tags[0]) or its formatted value.
Playground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<JsonTreeViewer/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
When to use
- An API debugging panel or a "raw response" tab in an admin dashboard, where developers expand nested fields.
- Viewing payloads in webhook logs, event streams or audit logs.
- A read-only preview of config files, feature flags or CMS fields.
- Sample responses in developer documentation, where readers can copy field paths directly.
- Not a good fit: for folder and file structures, use File Tree.
Installation
npx webberui@latest add https://webberui.com/r/json-tree-viewer.en.jsonOr, once registries are configured in components.json, install it as @webberui/json-tree-viewer.
Usage
import { JsonTreeViewer } from "@/components/ui/json-tree-viewer";
<JsonTreeViewer
data={{ name: "Webber", stars: 1024, tags: ["ui", "react"], beta: false }}
defaultExpandDepth={2}
rootName="$"
/>Examples
Loading data from an API
Fetch the data in a Client Component and pass it to data; show a short message until it arrives.
"use client";
import * as React from "react";
import { JsonTreeViewer, type JsonValue } from "@/components/ui/json-tree-viewer";
export function ResponsePanel({ url }: { url: string }) {
const [data, setData] = React.useState<JsonValue | undefined>(undefined);
React.useEffect(() => {
fetch(url)
.then((res) => res.json())
.then((json: JsonValue) => setData(json));
}, [url]);
if (data === undefined) return <p className="text-sm text-neutral-500">Loading…</p>;
return <JsonTreeViewer data={data} rootName="response" />;
}Collapsing large data and capping the height
For deeply nested data, set defaultExpandDepth to 1 so only the first level is open, and use className on the container to cap its height and add a scrollbar.
<JsonTreeViewer
data={orderPayload}
defaultExpandDepth={1}
rootName="order"
className="max-h-96 overflow-auto rounded-lg border p-3"
/>Passing data from a Server Component page
JsonValue only contains serializable types, so data fetched on the server can go straight into this Client Component as a prop.
// app/admin/webhooks/[id]/page.tsx
import { JsonTreeViewer } from "@/components/ui/json-tree-viewer";
import { getWebhookEvent } from "@/lib/webhooks";
export default async function WebhookEventPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const event = await getWebhookEvent(id);
return (
<section className="space-y-3">
<h1 className="text-lg font-semibold">Event {id}</h1>
<JsonTreeViewer data={event.payload} rootName="payload" defaultExpandDepth={Infinity} />
</section>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
data | JsonValue | — | The JSON data to render (an object, an array, or any primitive value) |
defaultExpandDepth | number | 2 | How many levels are expanded by default; 0 collapses everything, Infinity expands everything |
rootName | string | "$" | Root name used when copying a path, for example $ or data |
className | string | — | Forwarded to the outermost container and merged with the built-in styles |
Accessibility
- Expand/collapse is a native
buttonwitharia-expandedand a Chinesearia-labelcontaining the node path, operable with both Enter and Space from the keyboard - The "copy path / copy value" buttons also appear on keyboard focus (
focus-visible), so they do not depend on mouse hover - A successful copy gives visual feedback by switching the icon to a checkmark, and the button stays visible for 1.4 seconds
- When the user has "reduce motion" enabled at the system level, expanding, collapsing, and the arrow rotation switch instantly instead, with no height animation played
FAQ
Does it work with the Next.js App Router?
Yes. The component file already starts with "use client", so import it from a Server Component page and pass the data in. The clipboard and timer only touch navigator and window when the user clicks copy, so server rendering is unaffected.
What format are the copied paths in?
The root uses rootName (default $). Valid JavaScript identifiers use dot notation, while array indexes and keys with characters such as hyphens or CJK text use brackets, for example $.items[0]["shipping-address"]. The value button copies JSON.stringify output indented by 2 spaces.
I changed defaultExpandDepth and nothing happened.
Each node reads defaultExpandDepth only when it first mounts; after that the user controls expanding and collapsing. To reapply it to the whole tree, give JsonTreeViewer a new key so it remounts.
Will large data be slow?
Collapsed nodes do not render their children, but there is no virtual scrolling, so everything expanded stays in the DOM. For large data, keep defaultExpandDepth low and cap the height with className.
Why didn't copying work?
The component uses navigator.clipboard.writeText, which browsers only expose in secure contexts such as HTTPS or localhost. A failed write is silently ignored, though the checkmark still appears. Dependencies are motion, lucide-react, clsx and tailwind-merge.