Skip to content

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.

ts
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.

ts
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 autocomplete

FormStep ​

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.

ts
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.

ts
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.

ts
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.

ts
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.

ts
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.

ts
type ValidatorFn = (
  value: unknown,
  allValues: Record<string, unknown>,
) => string | null;

StepValidateFn ​

Async step-level validator. See Async Step Validation for the full guide.

ts
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:

json
{
  "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:

ts
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:

ts
import schema from '@formhaus/core/schema.json' with { type: 'json' }

Point editors at it with $schema:

json
{
  "$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.

PropertyAdded in
description on FormDefinition0.10.0
layout on fields0.10.0
skip on steps0.8.0
routes on steps0.7.0
next: false on steps0.7.0
autoAdvance on fields0.7.0

Formhaus is MIT licensed. Legal