A fully-customizable, collapsible, syntax-highlighted JSON tree viewer component for React. Built with shadcn/ui, Base UI, and Tailwind CSS v4. Ready to be copy-pasted into your project.
npx shadcn@latest add https://json-view-cn.vercel.app/r/json-view.jsonOr, if the @json-view-cn namespace is configured (see Registry):
npx shadcn@latest add @json-view-cn/json-viewThe component is added to your ui alias (e.g. components/ui/json-view.tsx).
Note: The component targets shadcn/ui projects that use Base UI primitives (e.g. the
base-mirastyle). It relies on the Base UIrenderprop ofTooltipTrigger, so projects using the Radix-based shadcn/ui components will need to adapt the tooltip usage (e.g. toasChild).
The component relies on these shadcn/ui components:
buttontooltip
And the following packages:
lucide-reactclsx/tailwind-merge(via thecnutility)
import { JsonView } from "@/components/ui/json-view"
const data = {
name: "John Doe",
age: 32,
isActive: true,
address: {
city: "Springfield",
coordinates: { lat: 39.78, lng: -89.65 },
},
roles: ["admin", "editor"],
}
export default function Page() {
return <JsonView data={data} />
}| Prop | Type | Default | Description |
|---|---|---|---|
data |
unknown |
— | The value to display. Required. Plain JSON works best, but non-JSON values are handled too. |
className |
string |
— | Additional classes for the outer wrapper. |
defaultExpanded |
boolean |
true |
Whether all nodes start expanded or collapsed. |
initialDepth |
number |
— | Expand nodes whose depth is lower than this value (the root is depth 0, so 1 expands only the root). Overrides defaultExpanded when set. |
indentGuide |
boolean |
true |
Show vertical indent guide lines along nested levels. |
rootName |
string |
— | Optional label displayed as the root node key. |
stringTruncate |
number |
0 |
Truncate strings longer than this value. Hover to preview, click to expand. 0 disables truncation. |
theme |
JsonViewTheme |
— | Override syntax highlight colors. All fields optional. |
Both JsonViewProps and JsonViewTheme are exported as types.
Pass a theme prop to customize the colors of each JSON element. All fields are optional and fall back to sensible defaults that work in both light and dark mode.
interface JsonViewTheme {
key?: string // Key names — default: "text-blue-700 dark:text-blue-400"
string?: string // String values — default: "text-green-700 dark:text-green-400"
number?: string // Numbers and bigints — default: "text-orange-700 dark:text-amber-400"
boolean?: string // Booleans — default: "text-purple-700 dark:text-purple-400"
null?: string // null, undefined, functions, symbols, [Circular] — default: "text-gray-500 dark:text-gray-400 italic"
bracket?: string // Brackets, colons, commas — default: "text-gray-700 dark:text-gray-300"
lineHover?: string // Row hover highlight — default: "hover:bg-muted/50"
}<JsonView
data={data}
theme={{
key: "text-rose-500 dark:text-rose-400",
string: "text-yellow-600 dark:text-yellow-300",
number: "text-violet-600 dark:text-violet-400",
boolean: "text-cyan-600 dark:text-cyan-400",
null: "text-pink-500 dark:text-pink-400 italic",
bracket: "text-gray-500 dark:text-gray-400",
lineHover: "hover:bg-rose-50 dark:hover:bg-rose-950/20",
}}
/>data is typed as unknown, so you can pass values that are not strictly JSON. They are rendered as close as possible to what JSON.stringify would produce, without ever throwing:
| Value | Rendered as |
|---|---|
Objects with toJSON() (e.g. Date) |
The result of toJSON() — a Date shows its ISO string |
Map |
An object (keys converted to strings) |
Set |
An array |
bigint |
The number, unquoted |
undefined |
undefined |
| Functions | [Function: name] |
| Symbols | Symbol(description) |
| Circular references | [Circular] |
Strings and keys are escaped like JSON, so embedded quotes, backslashes, and control characters (e.g. \n) are displayed faithfully. Long values without spaces (URLs, tokens) wrap instead of overflowing the container.
<JsonView data={data} defaultExpanded={false} /><JsonView data={data} initialDepth={1} /><JsonView data={data} rootName="response" /><JsonView data={data} stringTruncate={80} /><JsonView data={data} indentGuide={false} />const circular: Record<string, unknown> = { name: "loop" }
circular.self = circular
<JsonView
data={{
createdAt: new Date(),
tags: new Set(["a", "b"]),
counts: new Map([["x", 1]]),
id: BigInt("9007199254740993"),
onClick: function handleClick() {},
circular,
}}
/><JsonView
data={data}
rootName="event"
initialDepth={2}
stringTruncate={60}
indentGuide
/>- Collapsible nodes — Click any object or array row to expand/collapse. Collapsed nodes show their item/key count; empty objects and arrays render inline.
- Syntax highlighting — Color-coded keys, strings, numbers, booleans, and null values. Fully customizable via the
themeprop. - Copy on hover — A copy button appears next to each line on hover or keyboard focus. Objects and arrays are copied as formatted JSON, strings as their raw text.
- Indent guide lines — Vertical lines connecting nested levels, with hover highlight. Togglable.
- String truncation — Long strings are truncated with a tooltip preview. Click to expand inline, click again to collapse.
- Depth-based expansion — Control how deep the tree auto-expands with
initialDepth. - Non-JSON values — Dates, Maps, Sets, bigints, functions,
undefined, and circular references are handled safely. - Faithful escaping and wrapping — Strings and keys are escaped like JSON; long unbroken values wrap.
- Keyboard accessible — Expand/collapse toggles, copy buttons, and truncated-string toggles are real buttons with focus rings and
aria-expanded/aria-labelattributes. - Dark mode — Works out of the box with light and dark themes.
- Self-contained — No external JSON viewer libraries. Just shadcn/ui primitives and Tailwind.
- Copy-paste ready — No global CSS required. Drop the component file into your project and use it.
The demo site and registry are built with:
- Next.js 16 with App Router
- React 19
- Tailwind CSS v4
- shadcn/ui v4 with Base UI primitives
- TypeScript
This repo is a shadcn registry. It is flat
and served from https://json-view-cn.vercel.app/r/:
| URL | Contents |
|---|---|
/r/registry.json |
The registry index (no content fields) |
/r/json-view.json |
The json-view registry item |
Both files are generated from the root registry.json by pnpm registry:build and
committed under public/r.
Add the registry to your project's components.json:
{
"registries": {
"@json-view-cn": "https://json-view-cn.vercel.app/r/{name}.json"
}
}Then install items by name:
npx shadcn@latest add @json-view-cn/json-viewpnpm install
pnpm dev # start the demo site
pnpm lint # run ESLint
pnpm typecheck # run the TypeScript compiler
pnpm registry:build # rebuild public/r from registry.jsonThe component source lives in registry/json-view/json-view.tsx. The demo page (app/page.tsx) and showcase (components/showcase.tsx) import it directly, and pnpm registry:build generates the installable files in public/r.
See CONTRIBUTING.md for more details.