# React JSON Viewer: Search, Copy Paths and Big Payloads

> A React JSON viewer for webhooks and admin screens: search that opens the matching branches, copy a value or its path, a phone layout and 10,000-item arrays.

- Author: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Published: Oct 16, 2026
- Category: [Component guides](https://wingo-ui.com/blog/category/components)
- Reading time: 9 min
- Canonical: https://wingo-ui.com/blog/react-json-viewer

## TL;DR

A good React JSON viewer renders the payload as a collapsible tree, colors values by type, and has a search that opens every branch containing a match instead of only highlighting rows that are already visible. It should also copy a value or its path (data.items[0].name or a JSON Pointer), stay usable on a phone, and render only the visible rows when the payload has thousands of entries. The Wingo UI JSON Viewer does all of this from one data prop.

You have a webhook payload, an API response or an AI tool call input, and you need to show it in an admin screen so someone can find the one field that went wrong. A raw `JSON.stringify` dump works until the payload has 40 keys and three levels of nesting. A React JSON viewer turns that dump into a tree people can open, search and copy from. Below: what the component has to do, the props that matter in the Wingo UI [JSON Viewer](https://wingo-ui.com/components/json-viewer), how it behaves on a phone and with 10,000-item arrays, and how it compares to the packages most people find first. We build Wingo UI, so we are not neutral; facts about other libraries link to their own docs.

## What should a React JSON viewer do?

It should let people open, search and copy from a payload of any size, on any device. Showing nested data is the easy part. Our checklist, and a fair test for any library:

- **Collapse and expand** objects and arrays, with a preview of what is inside a closed branch (the first keys, or the first values and a count).
- **Color by type**: strings, numbers, booleans, null and dates should look different, because "is this `0` or `"0"`?" is half of debugging an API.
- **Search that opens branches.** A hit inside a closed branch is useless if the viewer only highlights rows that are already rendered.
- **Copy a value or its path**, so the path can go straight into code or a bug report.
- **Large payloads** that do not freeze the tab: paging for long arrays and virtualization for long trees.
- **Keyboard and screen readers**: one Tab stop, arrow keys, and real tree semantics.
- **Phones**: rows big enough to tap and actions that do not depend on hover.

Install the component and give it data:

```bash
npx wingo-ui@latest add json-viewer
```

```tsx
import { JsonViewer } from "@/components/ui/json-viewer";

export function WebhookPayload({ payload }: { payload: unknown }) {
  return <JsonViewer data={payload} rootName="webhook" defaultExpandDepth={2} fileName="webhook" />;
}
```

The component file starts with `"use client"`, so you can render it straight from a Server Component as long as `data` is serializable and you pass no functions. If the value came from a database column as text, parse it first: the viewer does not parse strings that contain JSON, and an unparsed payload shows as one long string.

> Live demo (JSON Viewer): The webhook opens two levels deep. Open invoice.lines, then one line: closed branches preview their contents and a count. Hover a row to copy its value or path. Try it at [JSON Viewer](https://wingo-ui.com/components/json-viewer) and install it with `npx wingo-ui@latest add json-viewer`.

## How do I search inside a JSON tree?

The search runs over the whole payload and opens every branch that contains a hit, which is what most people mean when they look for a React JSON viewer with search. Type in the toolbar, or press Ctrl+F (⌘F on a Mac) while focus is inside the viewer. Every key or value that matches is highlighted, the branches that contain a hit open, and the first hit scrolls to the middle of the panel. Enter and Shift+Enter step through the hits, the counter reads "2 of 5", and Escape clears the search. Case and diacritics are ignored, and numbers, `true` and `null` match as their text.

Two props shape it. `searchIn` limits matching to `"keys"`, `"values"` or `"both"` (the default), and `searchable={false}` removes the box when the payload is small. To start a search from your own UI, for example from a log line that mentions an invoice number, take the API from `setApi`:

```tsx
"use client";

import { useEffect, useState } from "react";
import { JsonViewer, type JsonViewerApi } from "@/components/ui/json-viewer";

export function EventInspector({ event, highlight }: { event: unknown; highlight?: string }) {
  const [api, setApi] = useState<JsonViewerApi | null>(null);

  useEffect(() => {
    if (highlight) api?.search(highlight);
  }, [api, highlight]);

  return <JsonViewer data={event} rootName="event" searchIn="values" setApi={setApi} />;
}
```

The same API has `expandAll`, `collapseAll`, `expandToDepth(depth)`, `expandPath(path)` and `scrollToPath(path)`. `expandPath(["invoice", "lines"])` opens that branch and its ancestors, which is how you deep link to a field from an error message.

## How do I copy a value or its path?

Every row has two actions, Copy value and Copy path, and each input method reaches them differently. With a mouse they appear when you hover or focus the row. From the keyboard, `c` copies the value and Shift+C the path. On a touch screen, a long press opens a sheet with the same actions, the full value (and a date in local time), and Expand all from here.

`pathFormat` decides what Copy path produces:

| `pathFormat` | Copied path | Use it for |
| --- | --- | --- |
| `"js"` (default) | `data.items[0].name`, keys with spaces as `["full name"]` | Pasting into JavaScript or a bug report |
| `"pointer"` | `/items/0/name` | JSON Pointer, as JSON Patch and many schema tools use |

`rootPathName` replaces `data` at the start of JS paths, so `rootPathName="webhook"` copies `webhook.invoice.customer.name`. If you need the path in your own code, `onSelect(path, value)` fires when someone activates a value with Enter, a click or a tap, and the selected row stays highlighted. `formatJsonPath(path, "pointer")` turns the `JsonPath` array into text the same way the copy action does.

> Live demo (JSON Viewer): Type chips, line numbers and JSON Pointer paths are on: tap a value and the selected path under the tree changes. Try it at [JSON Viewer](https://wingo-ui.com/components/json-viewer) and install it with `npx wingo-ui@latest add json-viewer`.

## Which JSON viewer props matter most?

Most screens only set three or four. The full list with defaults is on the [JSON Viewer docs page](https://wingo-ui.com/components/json-viewer).

| Prop | Default | What it does |
| --- | --- | --- |
| `defaultExpandDepth` | `1` | Levels open on mount; `0` keeps the root closed |
| `rootName` | `""` | A key label on the root, such as `"payload"` |
| `maxStringLength` | `120` | Longer strings stop with a Show more button |
| `showTypes` | `false` | A small chip before each value: string, int, float, bool, null, date |
| `sortKeys` | `false` | Alphabetical keys on screen; copy and download keep the original order |
| `detectDates` | `true` | ISO date strings get the date color and a tooltip in local time |
| `height` / `maxHeight` | `0` / `480` | A fixed height, or growth up to a cap before it scrolls inside |
| `toolbar` | `true` | Search, expand and collapse all, copy JSON and download |

`size="sm"` (24px rows, 12px text) with `toolbar={false}` fits a compact card, such as a large tool result in an agent UI. The [Tool Call Timeline](https://wingo-ui.com/components/tool-call-timeline) prints each step's arguments and result in a Code Block, which is fine for small inputs; a compact viewer is the better fit when a tool returns a deep, nested result (the [AI agent tool call UI guide](https://wingo-ui.com/blog/ai-agent-tool-call-ui) covers the rest of that screen). To fill a resizable pane, give the root `className="h-full"` and set both `height={0}` and `maxHeight={0}`. Every string is in `labels`, with English defaults in `DEFAULT_JSON_VIEWER_LABELS`, and counts and dates follow `locale`.

## Is the JSON tree accessible?

Yes. The body is one Tab stop with `role="tree"`, and each row is a `treeitem` with its level, position and expanded state. Up and Down move, Page Up and Page Down jump 10 rows, Right opens a branch or steps into it, Left closes it or goes to the parent, Home and End jump to the first and last row, and `*` opens every sibling. Enter toggles a branch or selects a value, and the context menu key (or Shift+F10) opens the row's actions. Copy and download feedback is announced through a polite live region, so a screen reader hears "Copied".

## How does it behave on a phone?

Rows grow from 28px to 36px on touch screens. That is below the usual 44px touch target on purpose: at 44px a phone shows about 12 lines of the tree, which is too little context to read nested data, so the long-press sheet carries the actions instead of tiny row buttons. Long values wrap under their key below 512px of the viewer's width instead of scrolling sideways (`wrap="auto"`), and below the same width the toolbar folds its actions into a ⋯ menu. Row actions move from hover to a long press, so nothing depends on a pointer. If the payload is the main content of a screen, give it most of the viewport height; a 200px panel on a phone shows about five rows.

## How do I render very large JSON payloads?

It pages long arrays and virtualizes long trees. An array over 100 items opens as rows of pages, so a list of 10,000 records shows 100 closed rows labeled like `[0 … 99]`, and each opens its 100 items. Above `virtualThreshold` (300 visible rows by default), only the rows on screen are in the DOM, measured one by one with 12 rows of overscan. Long strings such as base64 files stop at `maxStringLength`, and a search hit further inside one shows the whole string. Circular references print as `[Circular → data.parent]` instead of crashing, and BigInt, NaN, Infinity, Maps and Sets get a readable form.

Two pitfalls worth knowing. `defaultExpandDepth` applies on mount only, so changing it later does nothing; call `api.expandToDepth(n)` or change the component's `key`. And Copy JSON and Download export everything in `data`, so strip tokens and personal data on the server before the payload reaches an admin page.

## Is there a react json view alternative that is still maintained?

Search "json tree react" and the same few packages come up. Here is what their own READMEs and docs document, as of October 2026:

| Library | Search | Copy | Editing | Notes |
| --- | --- | --- | --- | --- |
| [react-json-view](https://github.com/mac-s-g/react-json-view) | Not documented | Yes | Yes | README says it is no longer maintained and points to the microlink fork |
| [@microlink/react-json-view](https://github.com/microlinkhq/react-json-view) | Not documented | `enableClipboard` | `onEdit`, `onAdd`, `onDelete` | The replacement the original README suggests, base16 themes |
| [react-json-view-lite](https://github.com/AnyRoad/react-json-view-lite) | Not documented | Not included | Not included | Small, markup follows the W3C treeview example, v2 needs React 18+ |
| [react-json-tree](https://github.com/reduxjs/redux-devtools/tree/main/packages/react-json-tree) | Not documented | Not documented | Not documented | From Redux DevTools, base16 themes, `collectionLimit` groups long collections |
| [Geist JSON View](https://vercel.com/geist/json-view) | Highlighting through `highlightPattern`, no search box | Selectable text | Not documented | Keyboard navigation, `defaultExpandDepth` |
| [Wingo UI JSON Viewer](https://wingo-ui.com/components/json-viewer) | Built in, opens matching branches | Value, path, whole JSON, download | No | Virtualized, phone layout, source copied into your project |

Where another package is the better pick: if people need to edit the JSON in place, use @microlink/react-json-view, because our viewer is read only. If you want the smallest read-only collapsible JSON React component and do not need search or copy, react-json-view-lite is a reasonable choice. For a tree of folders or categories instead of raw data, the [Tree View](https://wingo-ui.com/components/tree-view) is a better fit than any JSON viewer.

## When is a code block enough?

When the JSON is short and fixed: an example request in docs, a config snippet, a sample response. Nobody needs to collapse five lines. The free [Code Block](https://wingo-ui.com/components/code-block) highlights JSON with `language="json"`, adds line numbers and a copy button, and costs nothing to install. Use the viewer when the data is live, long or nested, and people need to find something inside it. For streams of log lines with JSON attached, the [Log Viewer](https://wingo-ui.com/components/log-viewer) streams the lines and opens a line's JSON tree under it when you click or tap it, using this same viewer ([React log viewer guide](https://wingo-ui.com/blog/react-log-viewer)). When the tree is your own data, such as folders or permissions people tick and drag around, the [React tree view guide](https://wingo-ui.com/blog/react-tree-view) fits better than a JSON viewer.

> Live demo (Code Block): A short JSON sample in the Code Block: press the copy button in the header and the icon turns into a check. Try it at [Code Block](https://wingo-ui.com/components/code-block) and install it with `npx wingo-ui@latest add code-block`.

## How do I add the JSON viewer to a Next.js project?

Run the add command in a Next.js project with Tailwind CSS v4. The CLI copies the component into `components/ui`, adds the hooks and helpers it imports, and installs its npm packages, including `@tanstack/react-virtual` and `motion`:

```bash
npx wingo-ui@latest add json-viewer code-block
```

The JSON Viewer is part of Wingo UI Pro, which costs $8 a month, $80 a year or $150 once as of October 2026 ([Wingo UI pricing](https://wingo-ui.com/pricing)); the Code Block is free. For a full admin screen around it, the [Next.js admin dashboard tutorial](https://wingo-ui.com/blog/nextjs-admin-dashboard-tutorial) builds the pages, and the [React AI chat UI guide](https://wingo-ui.com/blog/react-ai-chat-ui) covers where tool call payloads appear in a chat. More guides live in the [component guides category](https://wingo-ui.com/blog/category/components), starting with the [React form components guide](https://wingo-ui.com/blog/react-form-components-guide).

## Components in this post

- [JSON Viewer](https://wingo-ui.com/components/json-viewer) (Pro): A collapsible JSON tree with type colors, search that opens the matching branches, copy value or path, and virtualization for huge payloads. Install: `npx wingo-ui@latest add json-viewer`
- [Code Block](https://wingo-ui.com/components/code-block) (Free): A React code block with syntax highlighting, line numbers, highlighted lines and a copy button, themed from a single accent color. Install: `npx wingo-ui@latest add code-block`

## FAQ

### How do I display JSON in React?

For a short, fixed snippet, JSON.stringify(value, null, 2) inside a pre element or a code block is enough. For payloads people need to explore, such as webhooks, API responses or tool call inputs, use a JSON viewer component that renders a collapsible tree with search and copy.

### Is react-json-view still maintained?

No. The original mac-s-g/react-json-view README says the author can no longer maintain it and points to @microlink/react-json-view as a replacement candidate. Check the peer dependencies of any fork against your React version before you install it.

### How do I search inside a JSON tree in React?

Match keys and values against the query over the whole data, not only the rendered rows, then open the ancestors of every hit and scroll to the first one. The Wingo UI JSON Viewer does this from its toolbar search, and setApi gives you search(query) to start a search from code.

### Can a React JSON viewer handle 10,000 items?

Yes, if it virtualizes. The Wingo UI JSON Viewer opens long arrays in pages of 100 and, above 300 visible rows, renders only the rows on screen; its docs include a 10,000 contact example.

### Should a JSON string be passed to the viewer as is?

No. Parse it first with JSON.parse inside a try block. The Wingo UI JSON Viewer does not parse strings that contain JSON, so an unparsed payload shows as one long string value.

---

Source: https://wingo-ui.com/blog/react-json-viewer
