Skip to main content

@canard/schema-form

@canard/schema-form npm versionlicense
yarn add @canard/schema-form

JSON Schema-driven React form engine with a two-layer plugin architecture: a Validator plugin for JSON Schema validation and a UI plugin for form rendering components. Declare your data shape once in JSON Schema — the library builds, validates, and manages the full form state tree automatically.

Live Demo​

Try the interactive form below to see what @canard/schema-form can build from JSON Schema alone. This conference registration form demonstrates conditional fields, computed values, and 10+ input types — all driven by a single schema.

More Examples

Explore 10 interactive examples covering if/then/else, oneOf, computed values, and more in the Examples section.

UI Plugins

Each form above is rendered with the antd6-plugin. See individual plugin pages for component catalogs: antd5 | antd6 | MUI | antd-mobile

Architecture​

JSON Schema
│
▼
nodeFromJSONSchema() ← builds a typed node tree
│
├── StringNode / NumberNode / BooleanNode / NullNode (terminal)
├── ObjectNode / ArrayNode (branch)
└── VirtualNode (conditional / computed)
│
▼
Plugin System
├── ValidatorPlugin ← compile(jsonSchema) → ValidateFunction
└── SchemaFormPlugin ← FormGroup / FormLabel / FormInput / FormError
+ formTypeInputDefinitions
│
▼
<Form> → React component tree

Plugin System​

The library ships with no default validator and no default UI components. Both must be supplied via plugins before rendering any form. Plugins are registered globally with registerPlugin() and affect every <Form> in the application.

  • Validator plugin — integrates any validation library (AJV, Zod, Yup, etc.) by implementing compile(jsonSchema).
  • UI plugin — provides FormGroup, FormLabel, FormInput, FormError renderer components and formTypeInputDefinitions for per-field input components.

Official plugin packages:

  • @canard/schema-form-ajv8-plugin — AJV 8 validator
  • @canard/schema-form-antd5-plugin — Ant Design 5 UI
  • @canard/schema-form-mui6-plugin — MUI 6 UI

Node Types​

NodeSchema typesStrategy
StringNode"string"terminal
NumberNode"number", "integer"terminal
BooleanNode"boolean"terminal
NullNode"null"terminal
ObjectNode"object"branch (BranchStrategy or TerminalStrategy)
ArrayNode"array"branch (BranchStrategy or TerminalStrategy)
VirtualNodeconditional / oneOf / anyOf branchesspecial

FormTypeInput Resolution Order​

When rendering a field, the library resolves which input component to use in this priority order (highest first):

  1. Direct FormTypeInput prop on Form.Group
  2. formTypeInputMap path mapping on <Form>
  3. Form-level formTypeInputDefinitions prop
  4. FormProvider formTypeInputDefinitions
  5. Plugin-provided formTypeInputDefinitions

Claude Code Integration​

@canard/schema-form ships coding-agent assets — the schema-form-skill expert skill, its knowledge base, and the schema-form-rule authoring rule — under docs/agents/. Each release publishes them alongside the package so AI assistants can reason about the form engine with package-specific context.

Inject those assets with @slats/agents-assets-sync (bin: inject-agents-settings). --agent decides where they land — claude, codex, or the vendor-neutral agents:

# Into ~/.claude (user-level)
npx -p @slats/agents-assets-sync inject-agents-settings --package=@canard/schema-form --agent=claude --scope=user

# Into the nearest existing .claude, walking up from cwd
npx -p @slats/agents-assets-sync inject-agents-settings --package=@canard/schema-form --agent=claude --scope=project

# The same assets into Codex locations (skills into .agents/, rules into AGENTS.md)
npx -p @slats/agents-assets-sync inject-agents-settings --package=@canard/schema-form --agent=codex --scope=project

# Preview / overwrite flags
npx -p @slats/agents-assets-sync inject-agents-settings --package=@canard/schema-form --agent=claude --scope=user --dry-run
npx -p @slats/agents-assets-sync inject-agents-settings --package=@canard/schema-form --agent=claude --scope=user --force

The CLI resolves @canard/schema-form/package.json via createRequire, reads its agents.assetPath (docs/agents), and applies hash-based diff into the selected agent's asset locations (skip unchanged, warn on divergence, require --force to overwrite).

AI Agent Reference

AI Reference​

Package: @canard/schema-form v0.10.5 Purpose: JSON Schema-driven dynamic form generation for React with two-layer plugin architecture.

Plugin system​

import type { SchemaFormPlugin } from '@canard/schema-form';
import type { ValidatorPlugin } from '@canard/schema-form';
import { registerPlugin } from '@canard/schema-form';

Form component​

import { Form } from '@canard/schema-form';
import type { FormProps } from '@canard/schema-form';
import type { FormHandle } from '@canard/schema-form'; // ref API: submit(), reset(), validate(), getValues()
import type { FormChildrenProps } from '@canard/schema-form'; // render-prop children
import type { FormLabelProps } from '@canard/schema-form';
import type { FormErrorProps } from '@canard/schema-form';

Node types​

import type { SchemaNode } from '@canard/schema-form';
import type { StringNode, NumberNode, BooleanNode, NullNode } from '@canard/schema-form'; // terminal
import type { ObjectNode, ArrayNode } from '@canard/schema-form'; // branch
import type { VirtualNode } from '@canard/schema-form'; // conditional

Enums​

import { NodeState } from '@canard/schema-form'; // Initialized | Mounted | Unmounted
import { ValidationMode } from '@canard/schema-form'; // OnChange | OnBlur | OnSubmit
import { NodeEventType } from '@canard/schema-form'; // ValueChange | StateChange | ...
import { SetValueOption } from '@canard/schema-form'; // Merge | Replace

Type guards & error utilities​

import { isArrayNode, isBooleanNode, isBranchNode, isNumberNode } from '@canard/schema-form';
import { isSchemaFormError, isJSONSchemaError, isUnhandledError, isValidationError } from '@canard/schema-form';
import { JSONPointer } from '@canard/schema-form';

Architecture​

  • No default validator or UI — both via plugins: registerPlugin(plugin)
  • Node tree: JSON Schema → nodeFromJSONSchema() → typed node tree
  • Input resolution: Direct prop > formTypeInputMap > form-level > FormProvider > plugin