JSON Schema examples
Each example pairs the schema shape with a live JsonSchemaForm Storybook demo. Edit the rendered controls to see how nested values, arrays, maps, and conditionals behave.
All examples use the same controlled React pattern. JSON Schema default values are placeholders; initialize value explicitly when a default must already exist in submitted state.
import { useState } from "react";import { JsonSchemaForm, type JsonSchemaObject,} from "@flanksource/clicky-ui/components";
export function Example({ schema }: { schema: JsonSchemaObject }) { const [value, setValue] = useState<Record<string, unknown>>({});
return ( <> <JsonSchemaForm schema={schema} value={value} onChange={setValue} /> <pre>{JSON.stringify(value, null, 2)}</pre> </> );}Scalar fields
Section titled “Scalar fields”Primitive properties map to their natural controls. A string renders as text, numbers use numeric behavior, booleans use a switch, enums use a combobox, and a plain string array uses the compact tag editor.
{ "type": "object", "required": ["name"], "properties": { "name": { "type": "string", "title": "Full name" }, "age": { "type": "integer", "title": "Age", "minimum": 0 }, "active": { "type": "boolean", "title": "Active" }, "role": { "type": "string", "title": "Role", "enum": ["admin", "editor", "viewer"] }, "tags": { "type": "array", "title": "Tags", "items": { "type": "string" } } }}Formatted strings and rich markdown
Section titled “Formatted strings and rich markdown”String format selects a specialized editor. Supported formats include date, date-time, textarea, percent, and md. Markdown fields load MDXEditor lazily and accept typed x-md-editor options.
{ "type": "object", "properties": { "publishedAt": { "type": "string", "format": "date-time", "title": "Published at" }, "summary": { "type": "string", "format": "textarea", "title": "Summary" }, "body": { "type": "string", "format": "md", "title": "Body", "x-md-editor": { "admonitions": true, "frontmatter": true, "tables": true } } }}The rendered demo focuses on format: "md". Date fields use the date picker, percent keeps the stored value numeric while displaying a percent suffix, and textarea uses a multiline text control. See the MdxEditorField API for the standalone editor surface.
Nested objects
Section titled “Nested objects”Objects with fixed properties recurse into headed sub-forms. Required markers and edits remain scoped to each nested object while onChange receives the rebuilt root value.
{ "type": "object", "properties": { "name": { "type": "string", "title": "Service name" }, "database": { "type": "object", "title": "Database", "required": ["host"], "properties": { "host": { "type": "string", "title": "Host" }, "port": { "type": "integer", "title": "Port" }, "credentials": { "type": "object", "title": "Credentials", "properties": { "user": { "type": "string", "title": "User" }, "password": { "type": "string", "title": "Password" } } } } } }}Arrays of objects
Section titled “Arrays of objects”When items is an object schema, the array opens by its visible column count: up to x-table-max-columns (default 4) scalar columns render as a grid, and wider items — or items holding a nested object or object list — render as accordion rows with one item open at a time and its fields labelled inline. A ⋮ menu on the array’s summary line switches between Grid, Stack form and Inline form over the same value. x-layout: "table" and x-array-display: "accordion" pick the opening view explicitly. Use x-array-display: "cards" to keep titled cards open, or "stacked" for full sub-forms. The x-item extension supplies item titles, summaries, and available actions.
{ "type": "object", "properties": { "servers": { "type": "array", "title": "Servers", "items": { "type": "object", "required": ["name"], "properties": { "name": { "type": "string", "title": "Name" }, "port": { "type": "integer", "title": "Port", "minimum": 0 }, "tls": { "type": "boolean", "title": "TLS" } } } } }}Editable maps
Section titled “Editable maps”An object with a schema-valued additionalProperties renders editable key/value rows. Declared properties can still have stronger controls, while extra keys use the additional-property schema.
{ "type": "object", "properties": { "labels": { "type": "object", "title": "Labels", "properties": { "environment": { "type": "string", "enum": ["dev", "staging", "prod"] } }, "additionalProperties": { "type": "string" } } }}The control can add, rename, and remove extra keys. Declared properties keep their schema-derived input, while every undeclared key uses additionalProperties.
Conditional fields
Section titled “Conditional fields”if and then branches add fields according to the current value. Switching the controlling enum immediately changes the effective property set.
{ "type": "object", "properties": { "notify": { "type": "string", "title": "Notify via", "enum": ["none", "email", "webhook"] } }, "allOf": [ { "if": { "properties": { "notify": { "const": "email" } }, "required": ["notify"] }, "then": { "required": ["address"], "properties": { "address": { "type": "string", "title": "Email address" } } } }, { "if": { "properties": { "notify": { "const": "webhook" } }, "required": ["notify"] }, "then": { "required": ["url"], "properties": { "url": { "type": "string", "title": "Webhook URL" } } } } ]}The renderer evaluates if/then clauses inside allOf. Hiding a branch does not delete its existing value; normalize the object in the host if inactive branch data must be removed before submission.
Presentation extensions
Section titled “Presentation extensions”Clicky UI’s x-* keywords change presentation without changing the stored value. This example combines segmented enums, icons, descriptions, a 12-column grid, spans, and input adornments.
{ "type": "object", "x-columns": 12, "properties": { "runtime": { "type": "string", "title": "Runtime", "enum": ["claude", "codex"], "x-enum-display": "segmented", "x-enum-icons": { "claude": "anthropic", "codex": "openai" }, "x-enum-descriptions": { "claude": "Claude Code runtime", "codex": "Codex runtime" }, "x-col-span": 12 }, "model": { "type": "string", "title": "Model", "x-input-prefix-icon": "sparkles", "x-col-span": 8 }, "temperature": { "type": "number", "title": "Temperature", "minimum": 0, "maximum": 2, "x-col-span": 4 } }}For the complete keyword list, see JSON Schema extensions.
Property rows
Section titled “Property rows”Use property mode for a detail view whose values can be edited individually. The following complete component starts with a populated service, expands its nested connection into aligned rows, and keeps the identifier read-only.
import { useState } from "react";import { JsonSchemaForm, type JsonSchemaObject,} from "@flanksource/clicky-ui/components";
const schema: JsonSchemaObject = { type: "object", required: ["name"], properties: { name: { type: "string", title: "Name" }, enabled: { type: "boolean", title: "Enabled" }, connection: { type: "object", title: "Connection", properties: { host: { type: "string", title: "Host" }, retries: { type: "integer", title: "Retries", minimum: 0 }, }, }, identifier: { type: "string", title: "Identifier", readOnly: true }, },};
export function ServiceProperties() { const [value, setValue] = useState<Record<string, unknown>>({ name: "Example service", enabled: true, connection: { host: "localhost", retries: 3 }, identifier: "service-01", });
return ( <> <JsonSchemaForm schema={schema} value={value} onChange={setValue} layout={{ mode: "properties" }} showPreferencesMenu={false} /> <pre>{JSON.stringify(value, null, 2)}</pre> </> );}Click Name, change its text, and cancel: the preview keeps the original value. Edit it again and save to commit the draft. Repeat with Connection → Host to check a nested value; Identifier has no edit button. The Storybook demo extends this example with credentials, an enum, and tags.