Components

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.

Client Only

Installation

pnpm dlx @docyrus/cli add @docyrus/ui-json-schema-designer
Required Packages(3 packages)
pnpm add @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities
UI Primitives(7 components)
npx shadcn@latest add button collapsible dropdown-menu scroll-area switch tabs tooltip

Overview

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.

SurfaceRole
Form tabAn 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 tabA read-only <pre> view of the live JSON Schema output.
ToolbarStrict 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) and enum.
  • Nested containers — object properties expand into a sub-table below the row; array items (including array<object> and array<enum>) get the same treatment.
  • Enum editor — string-with-enum + enum-of-arrays both surface a per-value description list.
  • + Add property everywhere — 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 in required[]).
  • Controlled or uncontrolled — works with value + onChange or defaultValue.
  • 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-property default and format, and the Undo / redo history are no longer surfaced — they collapsed into the row-based UX. The DesignerProvider, useDesignerContext, schemaToTree and treeToSchema exports 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:

GroupKeywords surfaced in the UI
Identitytype, description
Objectproperties, required (derived from Strict Mode), additionalProperties (derived from Strict Mode)
Arrayitems (scalar, object, or enum)
Enumenum (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

PropTypeDefaultDescription
valueJsonSchema—Controlled JSON Schema document. Re-imports the tree when it changes externally.
defaultValueJsonSchema—Initial JSON Schema for uncontrolled use.
onChange(schema: JsonSchema) => void—Called with the updated JSON Schema after every edit.
readOnlybooleanfalseDisables all editing — the designer becomes a viewer.
defaultStrictModebooleanfalseInitial state of the Strict Mode switch (OpenAI Structured Outputs rules).
onStrictModeChange(strictMode: boolean) => void—Fires when the user toggles the Strict Mode switch.
titlestring'JSON Schema'Header title.
classNamestring—Extra classes for the root container (overrides the default 640px height).
aiAssistantOpenboolean—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

PropTypeDefaultDescription
childrenReactNode—Wrapped subtree.
valueJsonSchema—Controlled JSON Schema document.
defaultValueJsonSchema—Initial JSON Schema for uncontrolled use.
onChange(schema: JsonSchema) => void—Fired with the updated JSON Schema after every edit.
readOnlybooleanfalseDisables editing for all descendants.
defaultStrictModebooleanfalseInitial state of the Strict Mode switch.

Exports

ExportDescription
JsonSchemaDesignerThe row-based designer component.
useApplyJsonSchemaOwns 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.
DesignerProviderLegacy 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.
useDesignerContextLegacy hook to read / mutate the old reducer-backed state inside a DesignerProvider.
schemaToTreeConvert a JSON Schema document into the legacy node tree.
treeToSchemaConvert a node tree into a JSON Schema document.
treeToJsonStringSerialize a node tree to a pretty-printed JSON string.
parseJsonToTreeParse a JSON string into a node tree (or return an error).
DEFAULT_SCHEMA_DIALECTThe default $schema dialect URI.
TOOLBOX_ITEMSLegacy palette type entries — no longer surfaced by the row-based UI.
TOOLBOX_CATEGORIESLegacy palette category names.

Type Exports

TypeDescription
JsonSchemaDesignerPropsProps for the JsonSchemaDesigner component.
DesignerProviderPropsProps for the DesignerProvider component.
JsonSchemaA pragmatic JSON Schema document shape (Draft 2020-12 / Draft-07 compatible).
JsonSchemaType'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array' | 'null'.
SchemaNodeA single node in the designer's internal editable tree.
DesignerView'tree' | 'json' — the active center-pane tab.
ToolboxItemDefLegacy palette entry shape — kept for backward compat with custom panels.
IJsonSchemaAiAssistantRenderContextArgument passed to renderAiAssistant — { open, width, onClose, schema, strictMode }. The live schema + strict-mode flag are forwarded so the slot can shape its prompts/tools.
IUseApplyJsonSchemaResultReturn shape of useApplyJsonSchema — { schema, setSchema, strictMode, setStrictMode, buildEditorContext, tools }.

On this page