easy-forms
Core concepts

The schema

FormSchema, groups, and questions — the typed shape that is the single source of truth.

A form is a FormSchema<TFormData>. It is plain, serializable data (apart from functions like validators and dependency compute).

Shape

interface FormSchema<TFormData> {
	id?: string;
	title?: string;
	description?: string;
	groups: Group<TFormData>[];
	wizard?: WizardConfig<TFormData>;
}
  • groups — the content of the form. Groups are recursive containers of questions (or nested groups). See Groups & layout.
  • wizard — when present, the form renders as a multi-step wizard instead of a single page. See Wizard.

A question

Every question shares a common base, then adds control-specific options:

interface BaseQuestion {
	key: string;          // unique field key — becomes the value key
	label: string;
	control: ControlType; // 'text' | 'number' | 'dropdown' | ...
	defaultValue?: TValue;
	description?: string;
	readOnly?: boolean;
	disabled?: boolean;
	hidden?: boolean;
	required?: boolean;          // usually derived from validators
	clearWhenHidden?: boolean;   // default true
	validators?: Validators<TValue, TFormData>;
	dependents?: Dependency<TFormData>;
	formatter?: (value: TValue) => TValue;
	meta?: Record<string, unknown>; // renderer escape hatch
}

The control string is the discriminator. Each control narrows its value type, so the available options and validators are type-checked. For example, a checkbox has a boolean value, so validators: { minLength: 3 } on it is a compile error.

See every control and its options in Form components.

Typing your form data

Provide TFormData to get full type-safety on keys and dependency sources:

interface ProfileData extends Record<string, unknown> {
	displayName: string;
	age: number | null;
	newsletter: boolean;
}

const schema: FormSchema<ProfileData> = {
	groups: [
		{ questions: [
			{ key: 'displayName', label: 'Display name', control: 'text' },
			{ key: 'age', label: 'Age', control: 'number' },
			{ key: 'newsletter', label: 'Newsletter', control: 'checkbox' },
			// { key: 'typo', ... }  // ❌ 'typo' is not a key of ProfileData
		] },
	],
};

Serializability

Apart from functions (validators, compute, loadOptions, custom component), a schema is plain JSON. That means you can store the static parts in a database or CMS and assemble forms at runtime, reattaching the functions on the client.