JSON Viewer React component for Tailwind CSS v4
v1.0.0Added in 1.0.0
A collapsible JSON tree with type colors, search that opens the matching branches, copy value or path, and virtualization for huge payloads.
Click or tap a branch to open or close it: the chevron turns and small branches slide open. Collapsed objects show their first keys and arrays their first values, with a count. Type in the search box (or press Ctrl+F / ⌘F while inside the viewer) and every key or value that matches is highlighted, its branches open and the first hit scrolls to the middle; Enter and Shift+Enter or the arrows step through the hits and the counter reads 2 of 5. Escape clears the search, a second Escape returns to the tree. With a mouse, hovering a row shows Copy value and Copy path; on a phone a long press opens a sheet that repeats the value (and a date in local time) with the same actions plus Expand all from here. The toolbar expands or collapses everything, copies the whole JSON and downloads it as a dated .json file; below 512px of width it folds into a ⋯ menu. The tree is one Tab stop: Up and Down move, Right opens or steps into a branch, Left closes it or goes to the parent, Home and End jump, * opens every sibling, Enter toggles a branch or selects a value, c copies the value and Shift+C its path, and the context menu key opens the row's actions. Long strings stop at maxStringLength with a Show more button (Right and Left on the row show and hide the rest), arrays over 100 items open in pages of 100, and above 300 visible rows only what is on screen is rendered.
JSON Viewer is a Pro React component in the React data display components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add json-viewer, together with Action Sheet, Button, Empty State, Search Input, Tooltip and 10 more and the npm packages @tanstack/react-virtual, motion and lucide-react.
Add to a project
Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add json-viewerJSON Viewer is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans
Usage
Import JsonViewer from @/components/ui/json-viewer.
import { JsonViewer, type JsonViewerLabels } from "@/components/ui/json-viewer"
const labels: Partial<JsonViewerLabels> = {
search: "Search keys and values",
noMatches: "Nothing found",
}
const webhook = {
id: "evt_4912873366",
account: "acct_12345674",
created_at: "2026-09-24T16:38:12-04:00",
status: "ok",
invoice: { number: "2026-0412", customer: { name: "Acme Corp", tax_id: "12-3456740" } },
warnings: [],
}
export function WebhookDetails() {
return (
<JsonViewer
data={webhook}
rootName="webhook"
defaultExpandDepth={2}
fileName="invoice-2026-0412"
locale="en-US"
labels={labels}
onSelect={(path, value) => console.log(path, value)}
/>
)
}Props
data*unknownAny JSON-compatible value. Dates, undefined, BigInt (123n), NaN and Infinity show as words or numbers, Maps and Sets as objects and arrays, circular references as [Circular → data.parent]. Strings that contain JSON are not parsed: pass the parsed value. undefined shows the empty state.
rootNamedefault""string | falseA key label on the root, like "payload". Empty (false) shows the root braces only.
defaultExpandDepthdefault1numberLevels open on mount: 0 keeps the root closed, 1 shows its keys. Deeper opening goes through the API (expandToDepth).
expandedKeysstring[]Controlled open branches as pathKey strings ("$", "$/invoice/lines", pages of long arrays as "$/items/~g100").
defaultExpandedKeysstring[]Open branches on mount; replaces defaultExpandDepth when set.
onExpandedKeysChange(keys: string[]) => voidEvery open or close, including the branches a search opens.
searchabledefaulttruebooleanThe search box in the toolbar. Without it the toolbar shows the root name and its count.
searchIndefault"both""keys" | "values" | "both"What the search matches. Values match as their text (numbers, true, null too); case and diacritics are ignored.
toolbardefaulttruebooleanThe row with search, expand / collapse all, copy and download.
pathFormatdefault"js""js" | "pointer"Copy path as data.items[0].name (keys with spaces as ["full name"]) or as a JSON Pointer /items/0/name.
rootPathNamedefault"data"stringThe root identifier in js paths: data.invoice.customer.
maxStringLengthdefault120numberLonger strings stop with an ellipsis and a Show more button. A search hit further in shows the whole string.
showCountsdefaulttrueboolean3 keys / 12 items after objects and arrays.
showTypesdefaultfalsebooleanA small type chip before each value: string, int, float, bool, null, date, object, array.
showLineNumbersdefaultfalsebooleanA sticky gutter with row numbers that stays put while long lines scroll sideways.
quoteKeysdefaulttrueboolean"name": like JSON, or name: without quotes.
sortKeysdefaultfalsebooleanShows object keys alphabetically. Display only: copy and download keep the original order.
detectDatesdefaulttruebooleanISO date strings get the date color (still quoted) and a native tooltip with the local date and time.
sizedefault"md""sm" | "md"12px text on 24px rows or 13px on 28px rows. Rows grow to 36px on touch screens.
heightdefault0numberA fixed body height in px. 0 grows with the content up to maxHeight.
maxHeightdefault480numberThe tallest the body grows when height is 0; it scrolls inside after that.
virtualThresholddefault300numberVisible rows above which only the rows on screen are rendered (each row measured, 12 rows of overscan).
wrapdefault"auto""auto" | booleanLong values break onto more lines, indented under their key, instead of scrolling sideways. auto wraps below 512px of the viewer's width (phones).
onSelect(path: JsonPath, value: unknown) => voidA value activated with Enter, a click or a tap. With it set, the selected row stays highlighted.
fileNamedefault"data"stringDownload name base; the file is saved as data-2026-09-24-1640.json.
downloadabledefaulttruebooleanThe Download action in the toolbar.
copyabledefaulttruebooleanThe Copy JSON action in the toolbar (indented, circular references and BigInts made safe).
rowActionsdefaulttruebooleanCopy value and Copy path on each row: on hover or focus with a mouse, on long press on touch screens.
loadingdefaultfalsebooleanSkeleton rows in place of the tree; the toolbar actions wait.
skeletonCountdefault8numberSkeleton rows while loading.
emptyStateReactNodeReplaces the built-in No data state shown when data is undefined.
setApi(api: JsonViewerApi) => voidCalled after mount (and when the API changes) with { expandAll, collapseAll, expandToDepth(depth), expandPath(path), scrollToPath(path), search(query) }.
localedefault""stringCounts and date tooltips. Empty uses the UIProvider locale, then the browser's.
labelsPartial<JsonViewerLabels>Every string: search, matches(i, n), noMatches, previousMatch, nextMatch, expandAll, collapseAll, copy, copyValue, copyPath, download, copied, copyFailed, downloaded(name), items(count, formatted), keys(count, formatted), more(count, formatted), less, expand, collapse, expandHere, moreActions, cancel, circular, empty, loading, tree. English defaults in DEFAULT_JSON_VIEWER_LABELS.
classNamesPartial<Record<"toolbar" | "summary" | "search" | "body" | "row" | "lineNumber" | "toggle" | "key" | "value" | "type" | "preview" | "count" | "actions" | "more" | "empty", string>>Extra classes for the inner parts.
classNamestringExtra classes for the root panel, merged last. Give it a height (h-full) and height={0} with maxHeight={0} to fill a pane.
Parts
formatJsonPath
functionThe path text copy path uses: formatJsonPath(["items", 0, "name"]) is data.items[0].name, with "pointer" /items/0/name. Signature: (path: JsonPath, format?: "js" | "pointer", rootName?: string) => string.
pathKey
functionThe key expandedKeys uses for a path: pathKey(["invoice", "lines"]) is "$/invoice/lines". Signature: (path: JsonPath) => string.
flattenJson
functionThe visible rows (nodes, array pages and closing braces) for a set of open keys, for tests and custom renderers. Signature: (value: unknown, expandedKeys: Iterable<string>, options?: { rootName?; sortKeys?; detectDates? }) => JsonRow[].
JSON_COLORS
constantThe value color classes, shared with anything that prints JSON inline (log metadata). Type: Record<"key" | "string" | "number" | "boolean" | "null" | "date" | "punctuation", string>.
jsonTypeOf
functionThe node type the viewer colors a value by: object, array (Sets too), string, number, boolean, null, undefined, or date for Date objects and ISO date strings. Signature: (value: unknown, detectDates = true) => JsonNodeType.
DEFAULT_JSON_VIEWER_LABELS
constantEvery English string the viewer prints or announces (search, match counts, copy and download feedback, item counts, menu entries). Spread it and override what you translate.
JsonPath
typeA path into the data as keys and indexes, e.g. ["items", 0, "name"]. onSelect and the copy actions use it.
JsonViewerLabels
typeThe shape of labels; plain strings plus functions for counts like items(count, formatted).
Examples
Search
Hits in keys and values light up, their branches open and the counter reads 1 of 2.
10,000 rows, virtualized
Long arrays open in pages of 100 and only the visible rows are in the DOM.
Select a value
onSelect with JSON Pointer paths feeding a side panel.
Tool call input
size sm with type chips and line numbers inside an AI tool call card.
Long strings
A base64 PDF truncated at 120 characters with a show more button.
On a phone
Long press a row for an action sheet; the toolbar folds into a menu.
Loading and empty
Skeleton rows while the payload loads, and the empty state without data.
Dependencies
Files
- components/ui/json-viewer.tsx
FAQ
Is the React JSON Viewer component free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install JSON Viewer?
Run npx wingo-ui@latest add json-viewer. It also adds the Wingo UI items it builds on (Action Sheet, Button, Empty State, Search Input, Tooltip and 10 more) and the npm packages @tanstack/react-virtual, motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/json-viewer.
How does JSON Viewer behave on phones?
With a mouse, hovering a row shows Copy value and Copy path; on a phone a long press opens a sheet that repeats the value (and a date in local time) with the same actions plus Expand all from here.
See also
Used in
More React data display components
Articles using this component
- React Diff Viewer in React 19: Split, Unified, Word Diffs
Component guides9 min read
- React JSON Viewer: Search, Copy Paths and Big Payloads
Component guides9 min read