Skip to main content

Core Concepts

JSON Schema as the Single Source of Truth

The form structure, field types, validation rules, and conditional logic are all encoded in a single JSON Schema object. The library parses this schema at mount time and constructs an internal node tree — one node per schema field. You never manually define form fields or validation rules in component code.

const schema = {
type: 'object',
properties: {
title: { type: 'string', minLength: 1 },
price: { type: 'number', minimum: 0 },
inStock: { type: 'boolean' },
tags: { type: 'array', items: { type: 'string' } },
},
required: ['title', 'price'],
};
// → ObjectNode
// ├── StringNode (title)
// ├── NumberNode (price)
// ├── BooleanNode (inStock)
// └── ArrayNode (tags)
// └── StringNode (items)

Node Tree

Each node is a reactive object that owns its own value, validation errors, and state flags (dirty, touched, validated). Changes propagate upward: when a leaf node's value changes, all ancestor nodes receive an UpdateValue event and update their aggregated value.

Node State Flags

PropertyTypeDescription
valueanyCurrent value
defaultValueanyValue at initialization
errorsJsonSchemaError[]Validation errors for this node
dirtybooleanValue changed since initialization
touchedbooleanUser has interacted with this field
validatedbooleanValidation has been run
visiblebooleanComputed — controls visibility
activebooleanComputed — controls active state
readOnlybooleanComputed or inherited
disabledbooleanComputed or inherited
initializedbooleanNode has completed initialization
requiredbooleanField is required in parent schema
nullablebooleanSchema type includes "null"

Node Navigation

// Absolute path from root (RFC 6901 JSONPointer)
const node = rootNode.find('/address/street');

// Relative path from current node
const sibling = node.find('./city');

// Find all matching nodes
const allItems = rootNode.findAll('/items/*');

Plugin Architecture

The library is split into two independent plugin layers. You can mix and match any validator with any UI library.

┌─────────────────────────────────────────────────────┐
│ SchemaFormPlugin │
│ │
│ FormGroup FormLabel FormInput FormError │ ← UI layer
│ formTypeInputDefinitions │
├─────────────────────────────────────────────────────┤
│ ValidatorPlugin │
│ │
│ bind(instance) │ ← Validation layer
│ compile(jsonSchema) → ValidateFunction │
└─────────────────────────────────────────────────────┘

Plugin Merge Behavior

When multiple plugins are registered, the merge rules are:

PropertyMerge strategy
FormGroup, FormLabel, FormInput, FormErrorLast wins
formTypeInputDefinitionsPrepended (first match wins)
validatorLast wins
formatErrorLast wins

FormType System

A FormTypeInputDefinition pairs a test condition with a React component. The library evaluates all registered definitions in priority order and uses the first matching component to render a field.

The test can be a plain object (declarative) or a function (imperative):

// Object test — declarative
const definition: FormTypeInputDefinition = {
test: { type: 'string', format: 'date' }, // matches date fields
Component: DatePickerInput,
};

// Function test — imperative
const definition: FormTypeInputDefinition = {
test: (hint) => hint.jsonSchema.widget === 'richtext',
Component: RichTextEditor,
};

// Hint object available in test function:
type Hint = {
type: JsonSchemaType; // 'string' | 'number' | 'boolean' | ...
path: string; // JSONPointer path
required: boolean;
nullable: boolean;
jsonSchema: JsonSchema;
format?: string;
formType?: string; // custom jsonSchema.formType field
};

FormTypeInputMap (path-based override)

// Map specific paths to specific components
const formTypeInputMap: FormTypeInputMap = {
'/user/avatar': AvatarUploader,
'/items/*/price': CurrencyInput, // * matches any segment
};

<Form jsonSchema={schema} formTypeInputMap={formTypeInputMap} />

Computed Properties

Fields can react to sibling or parent values using JSONPointer expressions in the computed (or &computed) key. Dependencies are tracked automatically from the expression, or declared explicitly via watch.

{
type: 'object',
properties: {
isPremium: { type: 'boolean' },
discount: {
type: 'number',
computed: {
visible: '../isPremium === true', // show only for premium
readOnly: '../isPremium === false', // lock if not premium
watch: ['../isPremium'], // explicit dep (optional)
},
},
},
}

Supported computed keys: visible, active, readOnly, disabled, watch.

JSONPointer Extensions (computed scope only)

SyntaxMeaning
../fieldParent node's field
./fieldCurrent node's field (relative)
..Navigate to parent
*Wildcard — any segment (FormTypeInputMap only)

Schema Composition

The library supports all standard JSON Schema composition keywords. oneOf and anyOf branches can use the custom &if expression to bind activation to a field value.

// Standard if/then/else
{
if: { properties: { type: { enum: ['movie'] } } },
then: { properties: { releaseDate: { type: 'string', format: 'date' } } },
else: { properties: { version: { type: 'string' } } },
}

// oneOf with &if expression (exclusive branches)
{
oneOf: [
{ '&if': "./type === 'movie'", properties: { director: { type: 'string' } } },
{ '&if': "./type === 'game'", properties: { platform: { type: 'string' } } },
],
}

// anyOf with &if (non-exclusive, multiple can be active)
{
anyOf: [
{ '&if': './flag1 === true', properties: { field1: { type: 'string' } } },
{ '&if': './flag2 === true', properties: { field2: { type: 'string' } } },
],
}

// allOf (always merged)
{
allOf: [
{ properties: { firstName: { type: 'string' } } },
{ properties: { lastName: { type: 'string' } } },
{ required: ['firstName', 'lastName'] },
],
}

Conditional setValue Filtering

When a parent node calls setValue(), fields that do not match the active oneOf/anyOf branch are automatically removed from the value. When a child node calls setValue(), this filtering is bypassed — the value propagates regardless of the current branch condition.

Validation System

ValidationMode

enum ValidationMode {
None = 0, // No automatic validation
OnChange = 1, // Validate after every setValue()
OnRequest = 2, // Validate only when validate() is called
}

// Default: OnChange | OnRequest (bitwise OR)
<Form validationMode={ValidationMode.OnChange | ValidationMode.OnRequest} />

ShowError

enum ShowError {
Dirty = 1, // show when node.dirty === true
Touched = 2, // show when node.touched === true
DirtyTouched = 3, // show when both dirty AND touched (default)
}

<Form showError={ShowError.DirtyTouched} /> // default
<Form showError={true} /> // always show
<Form showError={false} /> // never show

Error Structure

interface JsonSchemaError {
dataPath: string; // JSONPointer to the failing field
keyword: string; // JSON Schema keyword (e.g., 'minLength', 'required')
message: string;
details: any; // keyword-specific details
source: any; // raw error from the validator library
}

Event System

Nodes communicate through a batched event system. Events scheduled in the same synchronous call stack are merged into a single batch and dispatched in the next microtask. UpdateValue is the exception — it fires synchronously (immediate mode) after initialization completes.

// Key event types (NodeEventType enum)
UpdateValue // value changed — synchronous after init
UpdateState // dirty/touched/validated changed
UpdateError // validation errors changed
UpdateComputedProperties // computed visible/readOnly/disabled changed
UpdateChildren // array/object children added or removed
RequestRefresh // internal: sync uncontrolled component
RequestRemount // external: force full component remount
AI Agent Reference

AI Concept Glossary

TermDefinition
SchemaNodeBase interface for all nodes — the public API surface
StringNode / NumberNode / BooleanNode / NullNodeTerminal nodes — no children
ObjectNode / ArrayNodeBranch nodes — have children
VirtualNodeNon-schema node created for conditional branches (oneOf/anyOf)
BranchStrategyStrategy for complex nested structures with child nodes
TerminalStrategyStrategy for simple non-nested structures
FormTypeInputDefinition{ test, Component } pair — maps a schema pattern to a React component
FormTypeInputMap{ [jsonPointerPath]: Component } — path-based component override
FormTypeInputPropsProps interface all custom input components must satisfy
FormTypeRendererPropsProps for FormGroup/FormLabel/FormInput/FormError renderer components
SchemaFormPluginPlugin object with optional renderer components + formTypeInputDefinitions + validator + formatError
ValidatorPlugin{ bind(instance), compile(jsonSchema) → ValidateFunction }
ValidateFunction(value) => JsonSchemaError[] | null | Promise<...>
computed / &computedJSON Schema extension for reactive field properties
&ifJSON Schema extension — expression to activate a oneOf/anyOf branch
enhancedValueValue including virtual/conditional field values — used for validation
ShowErrorEnum controlling when validation errors are displayed
ValidationModeEnum controlling when validation runs
NodeEventTypeEnum of all events nodes can emit
SetValueOptionBitwise options for setValue()Overwrite, Merge, Isolate
FormHandleRef interface for imperative form control
FormProviderContext provider for sharing plugin config across a subtree
JSONPointerRFC 6901 path string (e.g., /address/street) — extended with .., ., *