Display
Schema Display
An accessible API operation viewer with parameters, request and response schemas, recursive fields, depth guards, and four fetch states.
Preview in your theme
Loading preview…
Installation
npx shadcn@latest add https://ui.zyeon.ai/r/schema-display.jsonPrompt
Build a React + TypeScript + Tailwind "SchemaDisplay" component with
lucide-react and zod.
Contract
- Make one zod discriminated union the data source:
{ status: "loading"; message } |
{ status: "empty"; message } |
{ status: "error"; message } |
{ status: "ready"; operation }.
- operation contains method (GET / POST / PUT / PATCH / DELETE / HEAD /
OPTIONS), path, summary, optional description, parameters, an optional
request body, and responses.
- A parameter has id, name, location (path / query / header / cookie), type,
optional description, and optional required.
- Request and response bodies declare mediaType and schema fields. A response
also declares its HTTP status and description.
- Anchor the recursive field type with an explicit SchemaDisplayNode interface,
then validate it with z.ZodType<SchemaDisplayNode> + z.lazy(). Each node has
id, name, type, optional description / required / nullable / format /
enumValues, and a required children array; children: [] is a leaf. Keep each
id stable and unique among its siblings because the tree builds identity from
the ancestor-id path rather than array positions.
- Export SchemaDisplay, SchemaDisplaySection, and SchemaDisplayTree. Main props
extend the zod-derived state union plus native article attributes,
defaultExpandedDepth?: number (default 2), maxDepth?: number (default 6),
and onRetry?: () => void.
Behavior
- Treat loading, empty, error, and ready as first-class branches. Loading uses
an anatomy-matching skeleton and aria-busy; empty is a status message; error
is an alert and only renders Try again when onRetry exists.
- In ready state, render a safe text-tokenized method/path header (never
dangerouslySetInnerHTML), summary, parameters, optional request body, and
every response with status and media type. Empty section arrays get honest
"No … defined" copy rather than disappearing.
- Disclosure headings are native buttons with aria-expanded and aria-controls.
They start open and preserve their own state without a controllable-state
dependency.
- SchemaDisplayTree iteratively projects only visible nodes. It uses role=tree,
role=treeitem, aria-level / posinset / setsize, and one roving tab stop.
Arrow Up/Down move rows; Right expands or enters a child; Left collapses or
returns to the parent; Home/End jump; Enter/Space toggle.
- Build row keys from encoded ancestor ids. When the node set or maxDepth
changes, remove stale expansion keys and move a missing focus key to the
first remaining row; inserting/reordering siblings must not move state to a
different field.
- Clamp maxDepth to 1…12 and defaultExpandedDepth to 0…maxDepth. Use iterative
stacks for indexing, initial expansion, and visible-row projection. A branch
at the depth ceiling stays visible and reports how many nested fields were
hidden. An empty schema renders "No fields defined."
Rendering & styling
- Use semantic tokens only: bg-card, bg-muted, bg-primary/10, text-primary,
text-muted-foreground, bg-destructive/10, text-destructive, border, and ring.
Merge every public className with cn().
- Let field names, types, enum values, section metadata, and response media
types wrap or break inside the panel on narrow screens; do not turn a whole
row into max-content. Keep only code-like endpoint paths locally breakable.
- Use a focus-visible ring on disclosure, retry buttons, and the currently
keyboard-focused treeitem; roving state controls tabIndex but must not leave
a permanent ring on the last active row. Chevron transitions become immediate under
prefers-reduced-motion.
Customization levers
- Density: change section and row padding together; keep at least a 32px row
target and preserve the roving-focus ring.
- Information depth: adjust defaultExpandedDepth for first-paint scanning and
maxDepth for defensive rendering; do not remove the visible depth-limit note.
- Field metadata: omit enumValues / format / nullable upstream for a quieter
view, or add constrained badges beside the existing type badge.
- Method emphasis: remap METHOD_CLASS to the host's semantic token policy while
keeping method text visible so color is never the only signal.
- Composition: use SchemaDisplaySection and SchemaDisplayTree independently
inside an endpoint drawer or split-pane layout; keep the same contract and
keyboard model.Concepts
- Stable identity path — ancestor ids, not sibling positions, carry expansion and focus across insertions or reordering; missing ids are pruned when data changes.
- Visible-row projection — keyboard movement operates on the iteratively flattened rows readers can currently see, so collapsed descendants never become invisible focus targets.
- Bounded recursion — recursive contracts can be arbitrarily deep; a clamped
maxDepthand iterative traversal convert that risk into an explicit terminal row with a hidden-child count. - Safe path tokenization —
{projectId}segments are highlighted as React text spans, preserving untrusted path text without injecting HTML. - Disclosure before detail — parameters, request, and responses start open but remain independently collapsible, letting readers reduce density without losing endpoint context.
- Contract-driven four states — network lifecycle and operation data share one discriminated union, which prevents a partial ready payload from masquerading as a complete schema.