Json Schema Designer
A row-based JSON Schema designer ported from extend-hq's schema-builder. Edit property name, type, description and enum values inline; add children directly from each container; preview the live JSON Schema in the side tab.
Installation
pnpm dlx @docyrus/cli add @docyrus/ui-json-schema-designerpnpm add @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilitiesnpx shadcn@latest add button collapsible dropdown-menu scroll-area switch tabs tooltipOverview
JsonSchemaDesigner is a visual editor for JSON Schema
(Draft 2020-12 / Draft-07 compatible) based on extend-hq's
schema-builder
pattern. The UI is a single-pane editable table — there is no separate
toolbox, no drag-from-palette, no right-side properties inspector.
| Surface | Role |
|---|---|
| Form tab | An inline editable table. Each property is a row with name, type, description and (when relevant) a nested sub-table for object / array / enum content. + Add property lives at the bottom of every container. |
| JSON tab | A read-only <pre> view of the live JSON Schema output. |
| Toolbar | Strict Mode switch, Clear, and an AI Assistant toggle when renderAiAssistant is wired. |
It both renders / edits existing schemas (pass value or defaultValue)
and lets you design new schemas from scratch.
Features
- Row-based editing — property name, type and description live in the row itself. No popouts.
- Inline type select — a dropdown with the scalar JSON types plus
object,array(with a nested type sub-menu) andenum. - Nested containers — object properties expand into a sub-table below the row; array
items(includingarray<object>andarray<enum>) get the same treatment. - Enum editor — string-with-enum + enum-of-arrays both surface a per-value description list.
+ Add propertyeverywhere — at the bottom of the root, of each object, and of each array-of-object.- Drag-handle reorder — within a container, properties can be sorted via the handle on the left of each row (powered by
@dnd-kit). - Strict Mode — a single toolbar switch flips the output between "no required arrays" and "OpenAI Structured Outputs strict-mode rules" (
additionalProperties: false+ every key inrequired[]). - Controlled or uncontrolled — works with
value+onChangeordefaultValue. - Read-only mode — turns the designer into a schema viewer.
What changed from the previous version
The legacy three-pane drag-drop UI (left Toolbox, center Tree-View, right Item Properties) was replaced. Per-property required toggle, type-specific validation constraints (
minLength,pattern,minimum, …), per-propertydefaultandformat, and the Undo / redo history are no longer surfaced — they collapsed into the row-based UX. TheDesignerProvider,useDesignerContext,schemaToTreeandtreeToSchemaexports remain for any consumer that built a custom panel on top of the legacy state model.
Usage
import { JsonSchemaDesigner } from "@docyrus/ui/components/json-schema-designer";
export function SchemaPage() {
return (
<div className="h-[680px]">
<JsonSchemaDesigner title="Untitled Schema" />
</div>
);
}The component has a default height of 640px. Pass a className with an
explicit height (e.g. h-[720px] or h-full inside a sized parent) to
override it.
Editing an existing schema
Pass defaultValue for uncontrolled use — the schema is imported into the tree
once on mount:
<JsonSchemaDesigner
defaultValue={{
type: "object",
title: "User",
properties: {
email: { type: "string", format: "email" },
age: { type: "integer", minimum: 0 }
},
required: ["email"]
}}
/>Controlled mode
Pass value + onChange to drive the schema from your own state. onChange
fires with the full JSON Schema document after every edit:
import { useState } from "react";
import {
JsonSchemaDesigner,
type JsonSchema
} from "@docyrus/ui/components/json-schema-designer";
function ControlledDesigner() {
const [schema, setSchema] = useState<JsonSchema>({ type: "object" });
return <JsonSchemaDesigner value={schema} onChange={setSchema} />;
}Read-only viewer
<JsonSchemaDesigner value={schema} readOnly />Strict Mode (OpenAI Structured Outputs)
The header has a Strict Mode switch (off by default). When on, the designer constrains output to the rules required by OpenAI Structured Outputs:
- Every object emits
additionalProperties: false. - Every property is listed in
required— there is no per-property required toggle in the row UI; required is a single global flag.
Strict Mode is a non-destructive output overlay — toggling it never mutates
the row data, it only changes the generated schema. Set its initial value with
defaultStrictMode:
<JsonSchemaDesigner defaultStrictMode onChange={schema => callOpenAI(schema)} />Inside a dialog
The designer fills its container, so it drops cleanly into a large modal —
give the dialog content an explicit height and let the designer fill it with
className="h-full rounded-none border-0" (the preview above uses this
pattern).
import { useState } from "react";
import { JsonSchemaDesigner } from "@docyrus/ui/components/json-schema-designer";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle
} from "@/components/ui/dialog";
function SchemaDialog() {
const [open, setOpen] = useState(false);
return (
<>
<Button onClick={() => setOpen(true)}>Open Schema Designer</Button>
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="flex h-[85vh] max-w-[min(1120px,95vw)] flex-col gap-0 overflow-hidden p-0 sm:max-w-[min(1120px,95vw)]">
<DialogHeader className="shrink-0 border-b px-4 py-3 text-left">
<DialogTitle>JSON Schema Designer</DialogTitle>
</DialogHeader>
<div className="min-h-0 flex-1">
<JsonSchemaDesigner className="h-full rounded-none border-0" />
</div>
</DialogContent>
</Dialog>
</>
);
}Conversion helpers
The package exports pure helpers for converting between JSON Schema documents
and the designer's internal node tree — useful for persistence or building a
custom UI on top of DesignerProvider / useDesignerContext.
import {
schemaToTree,
treeToSchema,
treeToJsonString,
parseJsonToTree
} from "@docyrus/ui/components/json-schema-designer";
const tree = schemaToTree({ type: "object", properties: { id: { type: "string" } } });
const schema = treeToSchema(tree);
const json = treeToJsonString(tree);
const result = parseJsonToTree('{"type":"object"}');
if ("error" in result) console.error(result.error);
else console.log(result.root);Supported keywords
The new row-based UI is intentionally narrow. It maps onto the subset of JSON Schema that covers ~90% of agent / LLM authoring scenarios:
| Group | Keywords surfaced in the UI |
|---|---|
| Identity | type, description |
| Object | properties, required (derived from Strict Mode), additionalProperties (derived from Strict Mode) |
| Array | items (scalar, object, or enum) |
| Enum | enum (always under a string carrier), enumDescriptions per-value description |
title, default, format, minLength/maxLength/pattern,
minimum/maximum/multipleOf, minItems/maxItems/uniqueItems, and
per-property required flags are not part of the row UI. Pass a
controlled value containing those keywords and they are silently dropped
on the next round-trip (the inbound document parses, the outbound emit only
re-emits what the UI tracks). If you need them, drive the schema externally
and treat the designer as a read-only authoring surface.
EditorAgent integration
Pair useApplyJsonSchema with renderAiAssistant to let an LLM author JSON Schema documents. The hook owns the designer's controlled schema + strictMode state, ships an applyJsonSchema tool (which runs strict-mode validation and reports violations for self-correction) AND a buildEditorContext() helper that emits the current strict-mode flag plus an authoring hint into the system prompt.
import { EditorAgent } from '@docyrus/ui/components/editor-agent';
import { JsonSchemaDesigner, useApplyJsonSchema } from '@docyrus/ui/components/json-schema-designer';
export function JsonSchemaPlayground({ client, user, agentId }) {
const jsonSchema = useApplyJsonSchema();
const [aiOpen, setAiOpen] = useState(false);
return (
<JsonSchemaDesigner
value={jsonSchema.schema ?? undefined}
onChange={jsonSchema.setSchema}
onStrictModeChange={jsonSchema.setStrictMode}
aiAssistantOpen={aiOpen}
onAiAssistantOpenChange={setAiOpen}
renderAiAssistant={({ open, onClose }) => (
<EditorAgent
agentId={agentId}
client={client}
user={user}
open={open}
onClose={onClose}
clientTools={jsonSchema.tools}
editorContext={jsonSchema.buildEditorContext}
/>
)}
/>
);
}The matching backend agent must register a tool named applyJsonSchema whose input schema mirrors { schema: object (required), explanation?: string }.
API Reference
JsonSchemaDesigner
| Prop | Type | Default | Description |
|---|---|---|---|
value | JsonSchema | — | Controlled JSON Schema document. Re-imports the tree when it changes externally. |
defaultValue | JsonSchema | — | Initial JSON Schema for uncontrolled use. |
onChange | (schema: JsonSchema) => void | — | Called with the updated JSON Schema after every edit. |
readOnly | boolean | false | Disables all editing — the designer becomes a viewer. |
defaultStrictMode | boolean | false | Initial state of the Strict Mode switch (OpenAI Structured Outputs rules). |
onStrictModeChange | (strictMode: boolean) => void | — | Fires when the user toggles the Strict Mode switch. |
title | string | 'JSON Schema' | Header title. |
className | string | — | Extra classes for the root container (overrides the default 640px height). |
aiAssistantOpen | boolean | — | Controlled open state for the AI Assistant drawer. When provided the designer stops managing the open state internally — pair with onAiAssistantOpenChange. |
onAiAssistantOpenChange | (open: boolean) => void | — | Fired when the AI Assistant toolbar button toggles the drawer. |
renderAiAssistant | (ctx: IJsonSchemaAiAssistantRenderContext) => ReactNode | — | Mounts a custom AI Assistant drawer body. When set, the toolbar shows a Bot toggle that opens/closes the drawer; the render fn receives the live schema + strict-mode state. |
DesignerProvider
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Wrapped subtree. |
value | JsonSchema | — | Controlled JSON Schema document. |
defaultValue | JsonSchema | — | Initial JSON Schema for uncontrolled use. |
onChange | (schema: JsonSchema) => void | — | Fired with the updated JSON Schema after every edit. |
readOnly | boolean | false | Disables editing for all descendants. |
defaultStrictMode | boolean | false | Initial state of the Strict Mode switch. |
Exports
| Export | Description |
|---|---|
JsonSchemaDesigner | The row-based designer component. |
useApplyJsonSchema | Owns the controlled schema + strictMode state and ships the applyJsonSchema client-side tool for <EditorAgent>. Adds strict-mode validation so the agent can iterate to compliance, and exposes buildEditorContext() returning a strict-mode-aware system-prompt snippet. See EditorAgent integration below. |
DesignerProvider | Legacy state provider — kept for consumers that built a custom panel on top of useDesignerContext. Not wired into the new row-based UI; the designer manages its own internal state instead. |
useDesignerContext | Legacy hook to read / mutate the old reducer-backed state inside a DesignerProvider. |
schemaToTree | Convert a JSON Schema document into the legacy node tree. |
treeToSchema | Convert a node tree into a JSON Schema document. |
treeToJsonString | Serialize a node tree to a pretty-printed JSON string. |
parseJsonToTree | Parse a JSON string into a node tree (or return an error). |
DEFAULT_SCHEMA_DIALECT | The default $schema dialect URI. |
TOOLBOX_ITEMS | Legacy palette type entries — no longer surfaced by the row-based UI. |
TOOLBOX_CATEGORIES | Legacy palette category names. |
Type Exports
| Type | Description |
|---|---|
JsonSchemaDesignerProps | Props for the JsonSchemaDesigner component. |
DesignerProviderProps | Props for the DesignerProvider component. |
JsonSchema | A pragmatic JSON Schema document shape (Draft 2020-12 / Draft-07 compatible). |
JsonSchemaType | 'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array' | 'null'. |
SchemaNode | A single node in the designer's internal editable tree. |
DesignerView | 'tree' | 'json' — the active center-pane tab. |
ToolboxItemDef | Legacy palette entry shape — kept for backward compat with custom panels. |
IJsonSchemaAiAssistantRenderContext | Argument passed to renderAiAssistant — { open, width, onClose, schema, strictMode }. The live schema + strict-mode flag are forwarded so the slot can shape its prompts/tools. |
IUseApplyJsonSchemaResult | Return shape of useApplyJsonSchema — { schema, setSchema, strictMode, setStrictMode, buildEditorContext, tools }. |