Validation
Use JSON Schema 2020-12 constraints, Ajv validation, and the renderable subset together.
Schemaform validates form data with JSON Schema draft 2020-12 using Ajv. In the domain language, JSON Schema is the data-and-validation layer of a Form Definition.
Validation constraints belong in JSON Schema:
requiredsays which submitted properties must existtypesays what kind of value is validformathandles values such as email addressesminLength,maxLength,minItems, andmaxItemsdefine size limitsenumlimits a value to a known set of choices
The UI Schema decides presentation. It can place a field on a Page, group Controls, choose a Widget such as textarea, or choose when errors are revealed. It does not decide whether a value is valid.
Interactive validation
The example starts with invalid values. Blur a field or submit the form to reveal its errors, then correct the value and the feedback clears as the Working State changes.
Live form
Source
import type { FormDefinition } from "@mackehansson/schemaform";import { SchemaForm } from "@mackehansson/schemaform-shadcn";const signupForm = { formatVersion: 1, schema: { type: "object", required: ["email", "password"], properties: { email: { type: "string", title: "Email", format: "email", }, password: { type: "string", title: "Password", minLength: 8, }, bio: { type: "string", title: "Bio", maxLength: 120, }, }, }, uiSchema: { revealErrors: "onBlur", pages: [ { type: "Page", title: "Sign up", children: [ { type: "Control", scope: "#/properties/email" }, { type: "Control", scope: "#/properties/password" }, { type: "Control", scope: "#/properties/bio", widget: "textarea", }, ], }, ], }, rules: [],} satisfies FormDefinition;export function App() { return ( <SchemaForm definition={signupForm} onSubmit={(data) => console.log(data)} /> );}How errors surface
Core's Validator receives the JSON Schema and current Working State, then returns field errors keyed by JSON Pointer. The React hook merges those schema errors with any custom validators, tracks when errors should be revealed, and exposes helpers like errorsFor(pointer).
The Renderer decides how those errors look. In schemaform, each Widget receives the visible errors for its Control and renders them next to the input. The drop-in <SchemaForm> also blocks submit while visible fields are invalid; a blocked submit reveals all field errors.
You can control reveal behavior in the UI Schema or through a Renderer prop:
const definition = {
formatVersion: 1,
schema,
uiSchema: {
revealErrors: "onSubmit",
pages: [
{
type: "Page",
title: "Account",
children: [{ type: "Control", scope: "#/properties/email" }],
},
],
},
rules: [],
};"onBlur" reveals a field's errors after that field is first blurred. "onSubmit" keeps field errors hidden until a submit attempt, or until gated navigation blocks the user on a page.
Renderable subset vs validation
JSON Schema is larger than any practical form renderer. Schemaform therefore has a documented Renderable Subset: the Form Builder only emits JSON Schema constructs that the Form Renderer knows how to display as Controls.
Validation is broader than rendering. Ajv still validates full JSON Schema 2020-12 with strict: false, even when a hand-written imported schema contains constructs outside the Renderable Subset. The Renderability Check is the tool that tells you whether a schema can be rendered as a form; the Validator tells you whether the current data satisfies the schema.
Use the distinction this way:
| Need | Use |
|---|---|
| Validate submitted data | JSON Schema + Ajv |
| Decide which fields can be rendered | Renderable Subset + Renderability Check |
| Choose the visual widget for a field | UI Schema Control |
| Change required state based on Working State | Rule |
The rule of thumb from the three-layer model still applies: if it changes what data is valid, put it in JSON Schema. If it changes how a field appears, put it in UI Schema. If it changes behavior based on current data, put it in Rules.