Definition Reference
Every form is a JSON object following the FormDefinition type. The specification defines how core interprets each property.
FormDefinition
Top-level structure. Use fields (single-step) or steps (multi-step), never both. The FormEngine constructor throws when both are non-empty.
interface FormDefinition {
$schema?: string; // JSON Schema URL for editor validation
id: string; // Unique form identifier
title: string; // Form title for host UI and Figma output
description?: string; // Text under the title, up to 1000 characters. Added in 0.10.0
submit: FormAction; // Submit button config
cancel?: FormAction; // Cancel button (optional)
fields?: FormField[]; // Single-step form
steps?: FormStep[]; // Multi-step form
}FormField
A single form field. type determines which UI component renders.
interface FormField {
key: string; // Key in the values object
type: FieldType; // What to render (see Field Types page)
label: string; // Label text
placeholder?: string; // Placeholder text
helperText?: string; // Hint text between the label and the control
defaultValue?: unknown; // Pre-filled value
autoAdvance?: boolean; // Advance on explicit field activation. Added in 0.7.0
show?: ShowCondition[]; // AND conditions for visibility
showAny?: ShowCondition[]; // OR conditions for visibility
validation?: FieldValidation; // Validation rules
options?: FieldOption[]; // For select, radio, multiselect
layout?: 'stack' | 'inline'; // Radio and multiselect option layout. Added in 0.10.0
optionsFrom?: string; // Dynamic options provider key
optionsDependsOn?: string[]; // Re-fetch options when these fields change
accept?: string; // File input mime types
rows?: number; // Textarea row count
mask?: string; // Input mask pattern (shown as placeholder, custom components get full access)
inputMode?: 'text' | 'numeric' | 'decimal' | 'tel' | 'email' | 'url' | 'search';
}
type DefaultFieldType =
| 'text' | 'email' | 'phone' | 'number' | 'password'
| 'select' | 'autocomplete' | 'multiselect'
| 'checkbox' | 'radio' | 'switch'
| 'file' | 'date' | 'datetime' | 'textarea';
type FieldType = DefaultFieldType | (string & {}); // any string, built-ins get autocompleteFormStep
A step in a multi-step form. Has its own fields and optional CTA overrides. Step ids must be unique; the FormEngine constructor throws on duplicates.
interface FormStep {
id: string;
title: string;
description?: string;
fields: FormField[];
show?: ShowCondition[]; // Step-level visibility
showAny?: ShowCondition[];
routes?: StepRoute[]; // Ordered forward destinations. Added in 0.7.0
next?: FormAction | false; // Override or hide "Continue" button
back?: FormAction | false; // Override or hide "Back" button
skip?: FormAction; // Show a "Skip" button. Added in 0.8.0
}FormAction
Button configuration for submit, cancel, next, back, and skip.
interface FormAction {
label: string;
variant?: 'primary' | 'secondary' | 'text'; // Button class for back, skip and cancel
action?: string; // Identifier for custom actions
disabled?: ShowCondition[]; // Conditions available to the action renderer
}The default React and Vue adapters evaluate disabled on the top-level submit action. Custom action components receive the full action objects and can use variant, action, and disabled for other buttons.
ShowCondition
Conditional visibility for fields and steps. See Conditional Fields.
interface ShowCondition {
field: string; // Key of another field
eq?: string | number | boolean; // Equals
neq?: string | number | boolean; // Not equals
in?: (string | number)[]; // Value is in list
notIn?: (string | number)[]; // Value is not in list
notEmpty?: boolean; // Value is not null, undefined, '' or []
gt?: number; // Value is greater than
gte?: number; // Value is greater than or equal to
lt?: number; // Value is less than
lte?: number; // Value is less than or equal to
}FieldValidation
Built-in validation rules. See Validation.
interface FieldValidation {
required?: boolean | string; // true or custom message
minLength?: number;
minLengthMessage?: string;
maxLength?: number;
maxLengthMessage?: string;
min?: number; // For number fields
minMessage?: string;
max?: number;
maxMessage?: string;
pattern?: string; // Regex string
patternMessage?: string;
matchField?: string; // Must match another field's value
matchFieldMessage?: string;
validator?: string; // Custom validator key
}FieldOption
Options for select, radio, and multiselect fields.
interface FieldOption {
value: string;
label: string;
}ValidatorFn
Synchronous field-level validator. Registered via the validators prop. Return an error string, or null or '' for no error. The FormEngine constructor warns about a validator name that is not registered.
type ValidatorFn = (
value: unknown,
allValues: Record<string, unknown>,
) => string | null;StepValidateFn
Async step-level validator. See Async Step Validation for the full guide.
type StepValidateFn = (
stepId: string,
values: Record<string, unknown>,
) => Promise<Record<string, string> | null | void>;FormRenderer does not render the form title or description. Use them in your page heading or other host UI. The Figma plugin uses the title for frame names and headings and shows the description under it.
For runtime state and methods, see the FormEngine reference.
Minimal example
A working single-step form with two fields:
{
"id": "contact",
"title": "Contact Us",
"submit": { "label": "Send" },
"fields": [
{
"key": "name",
"type": "text",
"label": "Your name",
"validation": { "required": true }
},
{
"key": "message",
"type": "textarea",
"label": "Message",
"rows": 4,
"validation": { "required": true, "minLength": 10 }
}
]
}Explicit field activation
FormField.autoAdvance?: boolean enables forward navigation when a renderer field is intentionally committed. Built-in radio fields support click, Space and Enter; arrow keys only select. Custom React fields call onCommit(value) and Vue fields emit commit. Programmatic values and initial values never advance; the final step never auto-submits.
FormStep.next also accepts false to hide Next. This changes the action UI, not navigation permission. Provide an accessible retry action whenever Next is hidden. See the radio activation example.
Step routes
FormStep.routes?: StepRoute[] selects a forward path in routed forms:
interface StepRoute {
to: string | null
show?: ShowCondition[]
showAny?: ShowCondition[]
}The first matching route with a visible target wins. An unconditional last route is a fallback; no match continues to the next visible declared step. to: null makes this step terminal without submitting. Targets must be later declared step ids. Route conditions may reference this or earlier steps. Invalid targets and forward-field references are reported by checkDefinition(), and the FormEngine constructor throws on them. Step show/showAny conditions must reference earlier steps, and field conditions this or an earlier step, with or without routes; the constructor throws otherwise. checkDefinition() also warns when a branch falls through into a sibling branch.
With routes enabled, progress, Back, validation, hooks and submission use the active path. Skipped values remain in engine.values but are excluded from the active projection. See branch convergence, visibility, retained values and reconciliation and the complete JSON example.
JSON Schema
@formhaus/core ships a JSON Schema for FormDefinition, also served at https://formhaus.dev/schema/form-definition.json:
import schema from '@formhaus/core/schema.json' with { type: 'json' }Point editors at it with $schema:
{
"$schema": "https://formhaus.dev/schema/form-definition.json",
"id": "contact",
"title": "Contact Us",
"submit": { "label": "Send" },
"fields": [{ "key": "name", "type": "text", "label": "Your name" }]
}Version history
Definition properties added since 0.7.0. Earlier versions do not support them.
| Property | Added in |
|---|---|
description on FormDefinition | 0.10.0 |
layout on fields | 0.10.0 |
skip on steps | 0.8.0 |
routes on steps | 0.7.0 |
next: false on steps | 0.7.0 |
autoAdvance on fields | 0.7.0 |