API Reference

Complete documentation for the <json-viewer> custom element. Tag name: json-viewer. Class: JsonViewer (extends HTMLElement).

Installation

npm

npm install @profpowell/json-viewer

Then in your module:

import '@profpowell/json-viewer'   // side-effect import: defines <json-viewer>
// or
import { JsonViewer } from '@profpowell/json-viewer'
customElements.define('my-viewer', JsonViewer)

CDN

<script type="module" src="https://unpkg.com/@profpowell/json-viewer"></script>

No bundler

Drop dist/json-viewer.js next to your HTML and reference it directly:

<script type="module" src="./json-viewer.js"></script>
<json-viewer expanded="2"></json-viewer>

Attributes

All attributes are observed: changing them after the element is connected triggers a re-render.

AttributeTypeDefaultDescription
data JSON string โ€” Inline JSON data. Prefer the .data property for non-stringifiable values (Maps, BigInts, etc.).
src URL โ€” Fetched as JSON on connect and whenever the attribute changes. Errors render as { error: "<message>" }.
expanded int | true | false 1 Initial expansion depth. 0 opens the root only; integers open that many levels; true opens everything; false opens nothing.
mode tree | raw tree raw mode renders a pretty-printed JSON string in a <pre> block instead of the interactive tree.
show-types boolean false When present, type chips (string, number, array, โ€ฆ) appear next to each value.
show-sizes boolean false When present, array length and object/Map/Set entry counts appear in the container preview.
show-indices boolean false When present, array indices render as a leading 0:, 1:, โ€ฆ prefix on each item.
indent int (ch units) 2 Indentation width per level (in ch). Also controls the JSON indent in raw mode.

Properties

PropertyTypeDescription
.data unknown Read/write. Setting triggers a re-render and pre-builds the search-record index. Accepts any JS value โ€” primitive, plain object, array, Map, Set, Date, RegExp, BigInt, Function, or circular graph.

Methods

.expand(matcher) / .collapse(matcher)

Open or close every rendered container whose path matches matcher.

Matcher typeExampleBehavior
string (exact) 'users[0].name' Matches the exact path, or any path ending in .users[0].name / ]users[0].name.
string (glob) 'users[*].roles' * matches one path segment; ** matches zero or more.
RegExp /^users\[\d+\]\.email$/ Tested against the full path string.
function p => p.startsWith('config') Called with each rendered path; truthy result means a match.

.expandAll() / .collapseAll()

Open or close every currently-rendered container.

.search(query, options?)

Returns a navigable iterator over matches.

const it = viewer.search('error', { regex: false, caseSensitive: false })
it.count            // total matches
it.current?.path    // path of the focused match, or undefined
it.next()           // advance + return { value, done }
it.prev()           // step back
// also iterable: for (const m of viewer.search('foo')) { ... }

The search walks an internal record array built from .data, not the DOM. Only the ancestors of the focused match are expanded โ€” never the whole tree. Only the focused match is wrapped in <mark>.

Options:

OptionDefaultDescription
regexfalseTreat query as a regular expression source.
caseSensitivefalseMatch case exactly.

.entryAt(path)

Returns { key, value } for any path, including Map and Set entries where the key isn't a string. For object/array paths, key is the last segment; for Map entries it's the original key object (reference-equal to what you put in); for Set entries it's the integer index.

const adaKey = { id: 1 }
const cache = new Map([[adaKey, { hits: 42 }]])
viewer.data = { cache }
const entry = viewer.entryAt('cache@0')
entry.key === adaKey   // true โ€” reference preserved
entry.value.hits       // 42

.copy(text?)

Writes to the clipboard. With no argument, copies a BigInt-safe JSON snapshot of .data. With an argument, copies that string verbatim. Returns a Promise.

await viewer.copy()                    // full BigInt-safe JSON
await viewer.copy(viewer.entryAt('users[0]').value.email)

Events

Both events bubble and are composed: true so they cross the Shadow DOM boundary.

EventdetailFires when
json-viewer:keyclick { path, key, value } A key (the quoted property name in the tree) is clicked. The path-bar in the toolbar also opens.
json-viewer:valueclick { path, value, type } A primitive value (string, number, etc.) is clicked. type is the result of internal kindOf(): string, number, boolean, null, undefined, bigint, symbol, function, date, regexp, array, object, map, set.

Path grammar

Paths are flat strings. The grammar:

SegmentExampleMeaning
Object key (identifier)usersProperty access. Joined with . for nested keys.
Object key (special)["weird-key"]Bracket-quoted when the key isn't a valid JS identifier.
Array indexusers[0]Zero-based index in brackets.
Map / Set entrycache@0Insertion-order index, prefixed with @. Use .entryAt to recover the original key.
Root$The whole value.

Globs:

PatternMatches
*Exactly one path segment (no dots or brackets inside).
**Zero or more path segments โ€” used at the start or end of a glob, e.g. **.email.

CSS custom properties

Public theme contract. Each token resolves through a three-level fallback chain:

var(--json-viewer-FOO, var(--vb-FOO, light-dark(LIGHT, DARK)))

Set --json-viewer-* for direct overrides, set --vb-* at the page level for ecosystem-wide theming, or leave both unset for the built-in light-dark() defaults.

Public tokenVB fallbackDefault (light / dark)
--json-viewer-font-mono--vb-font-monosystem mono stack
--json-viewer-surface--vb-surface#ffffff / #1c1917
--json-viewer-text--vb-text#1c1917 / #f4f4f5
--json-viewer-text-muted--vb-text-muted#71717a / #a1a1aa
--json-viewer-border--vb-border#e7e5e4 / #3f3f46
--json-viewer-accent--vb-accent#0f766e / #2dd4bf
--json-viewer-radius--vb-radius0.5rem
--json-viewer-space-1..4--vb-space-1..40.25 / 0.5 / 0.75 / 1 rem
--json-viewer-key--vb-json-key#7e22ce / #c084fc
--json-viewer-string--vb-json-string#15803d / #4ade80
--json-viewer-number--vb-json-number#1d4ed8 / #60a5fa
--json-viewer-boolean--vb-json-boolean#a16207 / #fbbf24
--json-viewer-null--vb-json-null#71717a / #a1a1aa
--json-viewer-punct--vb-json-punct#52525b / #a1a1aa

CSS parts

Use ::part() to target inner regions for layout-level customization:

PartDescription
toolbarThe top action bar (Expand / Collapse / Search / Raw / Copy).
searchThe search panel (input + next/prev/close buttons).
path-barThe path-display strip that appears on key click.
pathThe <code> element inside the path-bar.
bodyThe scrollable container holding the tree (or the raw <pre>).

Example: borderless embed

json-viewer::part(toolbar) { display: none; }
json-viewer { border: none; }

Examples

Set data after fetching

const viewer = document.querySelector('json-viewer')
viewer.data = await fetch('/api/me').then(r => r.json())

React to clicks

viewer.addEventListener('json-viewer:keyclick', (e) => {
  console.log('clicked', e.detail.path, '=', e.detail.value)
})

Expand specific paths after a search miss

const it = viewer.search('errorCode')
if (it.count === 0) {
  viewer.expand('**.errors')   // expose error sections so a follow-up search hits them
}

Theme a single instance

<json-viewer style="--json-viewer-accent: #ff5722; --json-viewer-radius: 0">
</json-viewer>