Quick Start
Installation
# Core library
yarn add @canard/schema-form
# Validator plugin (AJV 8)
yarn add @canard/schema-form-ajv8-plugin ajv
# UI plugin — pick one
yarn add @canard/schema-form-antd5-plugin # Ant Design 5
yarn add @canard/schema-form-mui6-plugin # MUI 6
Register Plugins
Register plugins once at application entry point, before any
<Form> renders. The call is idempotent — duplicate registrations
are ignored by content hash.
src/main.tsx (or index.tsx)
import { registerPlugin } from '@canard/schema-form';
import { ajvValidatorPlugin } from '@canard/schema-form-ajv8-plugin';
import { antd5Plugin } from '@canard/schema-form-antd5-plugin';
registerPlugin(ajvValidatorPlugin);
registerPlugin(antd5Plugin);
Minimal Form
import { Form } from '@canard/schema-form';
const schema = {
type: 'object',
properties: {
name: { type: 'string', title: 'Name', minLength: 1 },
email: { type: 'string', title: 'Email', format: 'email' },
age: { type: 'number', title: 'Age', minimum: 0 },
},
required: ['name', 'email'],
} as const;
function MyForm() {
return (
<Form
jsonSchema={schema}
onSubmit={async (value) => {
console.log('submitted:', value);
}}
/>
);
}
With onChange and Ref
import { useRef, useState } from 'react';
import { Form, type FormHandle, ShowError } from '@canard/schema-form';
const schema = {
type: 'object',
properties: {
username: { type: 'string', title: 'Username', minLength: 3 },
password: { type: 'string', title: 'Password', minLength: 8 },
},
required: ['username', 'password'],
} as const;
function LoginForm() {
const formRef = useRef<FormHandle<typeof schema>>(null);
const [value, setValue] = useState<any>({});
const handleSubmit = async (data: typeof value) => {
await api.login(data);
};
return (
<>
<Form
ref={formRef}
jsonSchema={schema}
onChange={setValue}
onSubmit={handleSubmit}
showError={ShowError.DirtyTouched}
/>
<button onClick={() => formRef.current?.submit()}>
Login
</button>
</>
);
}
Custom Layout with Form.Group
Use Form.Group, Form.Label, Form.Input,
and Form.Error sub-components to build a custom layout.
Each takes a path prop (JSONPointer string).
<Form jsonSchema={schema} onSubmit={handleSubmit}>
<div className="form-row">
<Form.Group path="/firstName" />
<Form.Group path="/lastName" />
</div>
<Form.Group path="/email" />
<Form.Group path="/birthDate" />
</Form>
Using the Children Render Prop
<Form jsonSchema={schema}>
{({ value, errors, node }) => (
<>
<Form.Input path="/email" />
<Form.Error path="/email" />
{value?.role === 'admin' && <Form.Group path="/permissions" />}
<button disabled={(errors?.length ?? 0) > 0}>Submit</button>
</>
)}
</Form>
AI Agent Reference
AI Quick Reference
// 1. Register at app entry (once)
registerPlugin(validatorPlugin);
registerPlugin(uiPlugin);
// 2. Render
<Form
jsonSchema={schema} // required
defaultValue={initialValue} // optional
onChange={setValue}
onSubmit={async (value) => { /* validated value */ }}
onValidate={(errors) => { /* called after each validation */ }}
showError={ShowError.DirtyTouched} // default
validationMode={ValidationMode.OnChange | ValidationMode.OnRequest}
ref={formRef}
/>
// 3. Imperative API via ref
formRef.current?.submit()
formRef.current?.validate() // Promise<JsonSchemaError[]>
formRef.current?.getValue()
formRef.current?.setValue(val)
formRef.current?.reset()
formRef.current?.focus('/path')
formRef.current?.showError(true)
// 4. Submit with loading state
const { submit, pending } = useFormSubmit(formRef);