npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@juxtapos/json-viewer

v0.1.0

Published

Collapsible JSON tree viewer (React component + framework-free <json-viewer> custom element) with find, JSONPath rules, virtualization for large documents, keyboard navigation, and full custom-property theming.

Readme

This component was created because I wanted this exact feature set that I could reuse throughout my projects:

  • Collapsible JSON view with smooth transitions. I strongly dislike janky UIs.
  • Keyboard navigation
  • Custom formatting based on JSONPath.
  • Eventing for each selection.
  • Custom theming

Install

npm install @juxtapos/json-viewer

React 18+ is a peer dependency.

Usage

import { JsonViewer } from '@juxtapos/json-viewer'
import '@juxtapos/json-viewer/styles.css'

export function Example() {
  return <JsonViewer value={{ hello: 'world', items: [1, 2, 3] }} />
}

The viewer expects to live inside a flex column with a fixed height — give the parent display: flex; flex-direction: column and let the viewer flex-grow so its body scrolls independently.

View rules

A rule decorates matched nodes with custom styles and turns clicked primitives into actionable links. Paths are full JSONPath expressions (via jsonpath-plus):

const rules: ViewRule<MyAction>[] = [
  {
    id: 'highlight-ids',
    name: 'Highlight user IDs',
    enabled: true,
    target: '$..userId',
    action: { type: 'open-user' },
    style: 'color: orange; font-weight: 600',
  },
]

target is a JSONPath expression evaluated via jsonpath-plus. The leading $ is optional — users[*].id and $.users[*].id are equivalent. Matched primitives become clickable and call onRuleAction(rule, value, rawValue).

Custom rendering

A rule can replace the default rendering for nodes it matches by supplying render. Use it for hex-color swatches, image previews, mailto links, formatted timestamps — anything the default "value" chrome is wrong for.

const rules: ViewRule<MyAction>[] = [
  {
    id: 'r-color',
    name: 'Hex color swatch',
    enabled: true,
    target: '$..color',
    action: { type: 'noop' },
    render: ({ value }) =>
      typeof value === 'string' && /^#[0-9a-f]{6}$/i.test(value) ? (
        <span>
          <span
            style={{
              display: 'inline-block', width: 10, height: 10,
              background: value, marginRight: 4, borderRadius: 2,
            }}
          />
          "{value}"
        </span>
      ) : null,
  },
]

The renderer receives { value, path, rule }. Return null (or false) to fall back to the default rendering — useful when the renderer only wants to handle some matched values (e.g. valid hex strings) and leave the rest alone. When multiple matched rules supply render, the first one wins.

Custom element (no framework required)

The viewer also ships as a self-contained HTML custom element — React is bundled inside dist/element.js as an implementation detail, so the host page needs no framework:

<script type="module" src=".../@juxtapos/json-viewer/dist/element.js"></script>

<json-viewer toolbar style="height: 400px"></json-viewer>
<script>
  const el = document.querySelector('json-viewer')
  el.value = { hello: 'world', items: [1, 2, 3] }
  el.addEventListener('jv-selection-change', (e) => console.log(e.detail.path))
</script>

Or from a bundler: import '@juxtapos/json-viewer/element'.

Propertiesvalue, viewRules, sortKeys, expandAll, virtualizeThreshold, selectedPath (assign to drive selection), toolbar, expandState (read-only Map). Attributesvalue (JSON string), sort-keys, expand-all, virtualize-threshold, selected-path, toolbar. Events (bubbling, composed) — jv-selection-change {path}, jv-rule-action {rule, value, rawValue}, jv-node-contextmenu {path, value, rawValue, clientX, clientY}, jv-expand-change {path, expanded, recursive} (or null when the viewer auto-expanded ancestors to reveal a node).

View rules work unchanged, except render is framework-neutral: return an HTML string or a DOM Node instead of a React node.

The element renders into an open shadow root. Theming still works — set the --jv-* custom properties on the element or any ancestor; they inherit through the shadow boundary. Give the element a bounded height (it's a display: flex column host all by itself).

React hosts should keep using the JsonViewer component from the main entry — it's a fraction of the size and shares the host's React.

Large documents

Above 3000 total nodes the viewer automatically switches from the nested, animated DOM to a virtualized renderer: the visible tree is flattened into fixed-height rows and only the rows in (and just around) the viewport are mounted. Find, JSONPath rules, custom renderers, keyboard navigation, selection, and copy all behave identically — the only difference is that expand/collapse is instant instead of animated.

Tune the switchover with virtualizeThreshold:

<JsonViewer value={huge} virtualizeThreshold={0} />        // always virtualize
<JsonViewer value={data} virtualizeThreshold={Infinity} /> // never virtualize

In virtualized mode the viewer must have a bounded height (the flex-column layout described above already provides one). Row height is fixed and read from the --jv-row-height custom property (default 21px) — if you change the font size, set a matching row height.

Theming

Every color the viewer renders is exposed as a CSS custom property scoped to the viewer root (.jv-default). Override on any ancestor to retheme:

.jv-default {
  --jv-text-string: #ff7;
  --jv-text-number: #7df;
  --jv-bg-selected: rgba(74, 158, 255, 0.15);
}

The full token list is documented at the top of dist/styles.css.

Keyboard

  • ↑ / ↓ — move between siblings at the same level; falls off the edge to the parent or its next sibling.
  • — expand the focused node, then descend into its children.
  • — move to the parent node. Never collapses — leaving a node keeps it open.
  • Enter / Space — collapse or expand the focused node (with Alt: recursively, like alt-click).
  • Cmd / Ctrl + F — open find; supports substring or $ JSONPath expressions. Scoped to the viewer: it only fires while focus is inside the component, so the browser's own find-in-page keeps working elsewhere.
  • Cmd / Ctrl + C — copy the selected node as JSON (or the highlighted text if any).

License

MIT — see LICENSE.