Almost every app I work on ends up showing raw JSON somewhere: API responses, webhook payloads, debug panels. A <pre>{JSON.stringify(data, null, 2)}</pre> gets hard to read fast, and most JSON viewer packages bring their own styles and never quite match a shadcn/ui app.
At the same time, when reading raw data, logs, analytics events, etc., having a decent way to quickly visualise complex json payloads is quite important (or at least it is for me!)
So I built json-view-cn: a collapsible, syntax-highlighted JSON tree viewer you install the shadcn way. It's one file you own, styled with Tailwind.
mnove
/
json-view-cn
Fully-customizable, collapsible, syntax-highlighted JSON tree viewer. Ready to be copy-pasted.
json-view-cn
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.
Installation
npx shadcn@latest add https://json-view-cn.vercel.app/r/json-view.json
Or, if the @json-view-cn namespace is configured (see Registry):
npx shadcn@latest add @json-view-cn/json-view
The 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).
Dependencies
The component relies on these shadcn/ui components:
buttontooltip
And the following packages:
lucide-react-
clsx/tailwind-merge(via thecnutility)
Usage
import { JsonView } from "@/components/ui/json-view"
const data = {
name: "John Doe",
age: 32,
isActive: …Getting started
npx shadcn@latest add https://json-view-cn.vercel.app/r/json-view.json
import { JsonView } from "@/components/ui/json-view"
<JsonView data={response} rootName="response" initialDepth={1} stringTruncate={60} />
Features
-
Collapsible nodes with
…3 keys/…12 itemssummaries - Syntax highlighting that works in light and dark mode, themeable with plain Tailwind classes
- Copy on hover for any line (objects are copied as pretty-printed JSON)
- Indent guides, string truncation with tooltip preview, and depth control
-
Keyboard accessible, with real buttons, focus rings and
aria-expanded -
No extra dependencies beyond shadcn's
buttonandtooltipandlucide-react
Theming is just a map of class names:
<JsonView
data={data}
theme={{ key: "text-rose-500 dark:text-rose-400", string: "text-yellow-600 dark:text-yellow-300" }}
/>
The tricky parts (and what I learnt)
Real data isn't always JSON
In real app state you'll find Dates, Maps, bigint IDs and the odd stray function. So data is typed as unknown, and values are rendered as close as possible to what JSON.stringify would produce. It respects toJSON() (so a Date shows its ISO string), turns Map into an object and Set into an array, and shows bigint, undefined, functions and symbols without quotes so they can't be mistaken for strings. It never throws.
Circular is not the same as "seen before"
The obvious fix for circular references is to keep a set of every object you've visited. But that's wrong:
const address = { city: "Springfield" }
const data = { billing: address, shipping: address }
address appears twice, but nothing here is circular. A reference is only circular if it points back to one of its own ancestors. So each node passes its ancestor chain down to its children instead. The copy button does the same thing inside a JSON.stringify replacer, using the fact that this is the parent object of the value being serialized.
Escaping in one line
Strings with quotes, backslashes or newlines need to display the way they would in a .json file. The simplest way to do that is to let JSON escape them:
const escapeString = (value: string) => JSON.stringify(value).slice(1, -1)
These are just a few things of the bugs/edge cases I encountered when developing this.
Shipped as a shadcn registry
The repo is a shadcn registry. shadcn build generates static JSON that's served from the demo site, and the CLI installs button and tooltip for you if they're missing. You can also add the @json-view-cn namespace to components.json and run npx shadcn@latest add @json-view-cn/json-view.
Note: it targets shadcn/ui projects that use Base UI primitives. On the Radix-based components, swap the tooltip's
renderprop forasChild. That's a two-line change in a file you own. I might also add a Radix and React Aria version of this in the near future...
Try it
- 🔗 Demo: https://json-view-cn.vercel.app/
- ⭐ GitHub: https://github.com/mnove/json-view-cn
It's MIT licensed. If you find data that renders wrong, open an issue with the payload. Thanks for reading! 🙌

Top comments (0)