Form Builder
A drag-and-drop form designer with a field palette, canvas, properties panel, live preview and code export for TanStack Form, React Hook Form, Zod and raw useState.
Installation
pnpm dlx @docyrus/cli add @docyrus/ui-form-builderpnpm add @dnd-kit/core @dnd-kit/sortable nanoidnpx shadcn@latest add button input label switch select tabs scroll-area collapsibleUsage
import { FormBuilder } from "@docyrus/ui/components/form-builder";
export function FormBuilderPage() {
return (
<div className="h-[720px]">
<FormBuilder />
</div>
);
}The component fills its parent (flex h-full flex-col), so wrap it in a sized container or pass a className with explicit dimensions.
Reading builder state
useBuilderContext is exposed for consumers that want to render their own toolbar, persist drafts, or react to selection. It must be used inside a <BuilderProvider>, which <FormBuilder> already renders internally — to read state from outside the default UI, render <BuilderProvider> yourself and place <FormBuilder> (or its sub-components) underneath.
import {
BuilderProvider,
useBuilderContext
} from "@docyrus/ui/components/form-builder";
function FieldCount() {
const { state } = useBuilderContext();
return <span>{state.fields.length} fields</span>;
}Built-in field types
The palette ships with 9 categories covering 30+ field types from the Docyrus form-fields library. Each type maps to a DynamicFormField renderer, so what users build in the canvas is exactly what they get at runtime.
| Category | Field types |
|---|---|
| Text & Input | field-text, field-textarea, field-email, field-url, field-phone, field-color |
| Numbers | field-number, field-money, field-percent, field-rating, field-duration, field-currency |
| Selection | field-select, field-multiSelect, field-tagSelect, field-radioGroup, field-enum, field-status, field-approvalStatus |
| Date & Time | field-date, field-dateTime, field-time, field-dateRange |
| Toggle | field-switch, field-checkbox |
| Visual & Media | field-icon, field-avatar, field-file, field-image |
| Relation & User | field-userSelect, field-userMultiSelect, field-relation, field-locationSelect |
| Rich Content | field-htmlEditor, field-codeEditor, field-docEditor, field-emailEditor |
| Data Structure | field-taskList, field-schemaRepeater, field-queryBuilder |
Code export
Switch the toolbar's view mode to Code to render generated TSX for the current form. Four templates are available:
| Template | Output |
|---|---|
tanstack | useForm from @tanstack/react-form driving DynamicFormField |
rhf | useForm from react-hook-form with native inputs |
zod | react-hook-form + zodResolver and a generated z.object schema |
raw | useState per field, plain <input> markup |
The generated code uses user-project import paths (@/components/docyrus/form-fields, @/components/ui/button) so it drops straight into a project that has installed @docyrus/ui-form-fields and the matching shadcn primitives. Adjust the prefix if your project uses a different alias.
API Reference
<FormBuilder>
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | flex h-full flex-col | Override the root container classes. Useful for setting an explicit height or max width. |
initialState | Partial<BuilderState> | – | Seed document, shallow-merged over the built-in defaults (selectedFieldId forced to null). Takes precedence over the persisted draft. |
onChange | (state: BuilderState) => void | – | Called with the full document after every change, skipping the initial mount. |
persist | boolean | initialState === undefined | Restore from / save to the persist key. On by default for the standalone tool, off by default whenever initialState is supplied so a seeded builder never overwrites the standalone draft. |
persistKey | string | docyrus-form-builder-state | localStorage key backing persistence. Give each builder its own key when more than one can be mounted (or more than one form edited) in the same browser. |
useBuilderContext()
Returns the live builder state and dispatchers. Throws if called outside a <BuilderProvider>.
| Property | Type | Description |
|---|---|---|
state | BuilderState | The full reducer state (fields, selection, view mode, grid columns, form title). |
dispatch | Dispatch<BuilderAction> | Low-level dispatcher for every reducer action (including undo/redo, reorder, clear). |
selectedField | BuilderField | null | The currently focused field, or null when nothing is selected. |
addField | (componentType: string, index?: number) => void | Append (or insert at index) a new field of the given palette type. |
removeField | (id: string) => void | Remove a field by id. |
duplicateField | (id: string) => void | Clone a field by id and insert it directly after the source. |
selectField | (id: string | null) => void | Update the selection. |
canUndo | boolean | true when there is at least one entry in the undo stack. |
canRedo | boolean | true when there is at least one entry in the redo stack. |
<BuilderProvider>
Wraps children in the builder reducer and 50-step history; rendered automatically by <FormBuilder>. Accepts children plus the same initialState / onChange / persist / persistKey contract as <FormBuilder>.
Components
| Component | Description |
|---|---|
FormBuilder | The full builder experience (toolbar, palette, canvas, properties panel, preview, code export). Canvas and Preview render sections as real panels (own inner grid, panel width, optional hidden title) and apply the form-level label / size / style rules; Preview additionally honors readOnly and enforces every validation token. The properties panel has four tabs — General, Options (enum fields only), Validation, and Actions. The Validation tab supports required toggle, min/max/pattern constraints, and Custom Validations — JSONata-based rules evaluated on submit. Each custom validation rule has an expression (returns true to pass) and a message shown on failure. Supported field types: text, textarea, email, url, phone, number, money, percent, duration, date, dateTime, rating. |
BuilderProvider | Reducer + 50-step undo/redo history provider. |
useBuilderContext | Hook for reading builder state and triggering actions. |
generateCode | Standalone helper that turns a BuilderState into a TSX string for any of the four templates. |
FIELD_TYPE_CONFIGS | Registry map of every palette type → label, icon, default IField patch, seeded enum options and search tags. |
PALETTE_CATEGORIES | The nine palette categories with their ordered items. |
createDefaultBuilderField | Builds the default fieldConfig / enumOptions / customProps for a palette type (id + slug generation included). |
getPropertySchemas | Property schema list (common + type-specific) driving the General tab for a field type. |
hasEnumOptions | Whether a field type shows the Options tab. |
ENUM_FIELD_TYPES | Set of enum-backed field types. |
TEXT_VALIDATION_TYPES / NUMBER_VALIDATION_TYPES / CUSTOM_VALIDATION_TYPES | Sets describing which validation controls a field type exposes (length/pattern, min/max, JSONata rules). |
Type Exports
| Type | Description |
|---|---|
FormBuilderProps | Props for <FormBuilder> (className?). |
BuilderField | A single field on the canvas: id, component type, IField config, optional enum options and colSpan. |
BuilderState | Top-level reducer state — fields, selectedFieldId, viewMode, formTitle, formDescription, gridColumns. |
BuilderAction | Union of every dispatchable action (add/remove/reorder/duplicate/update/select/undo/redo/clear/set-meta/set-view-mode). |
PaletteCategory | A palette section (id, label, icon, items). |
PaletteItem | A single palette entry (component type, label, icon, default field type, search tags). |
PropertySchema | Metadata for one editable property in the right-hand properties panel. |
CodeTemplate | 'tanstack' | 'rhf' | 'zod' | 'raw' — accepted by generateCode. |
FieldAction | Trigger + ordered blocks definition stored on BuilderField.field.fieldActions. Authored through the Actions tab in the properties panel. |
FieldActionBlock | Named block with if / else-if / else branches and unconditional steps. |
FieldActionStep | Discriminated union of imperative commands: value writes, visibility, required, disabled, readOnly. |
BuilderSection | A container panel: id, title?, colSpan?, gridColumns?, panelWidth?, collapsible?, hideLabel?. Fields join it via BuilderField.sectionId. |
FormLayoutStyle | Form-level styling patch — labelAlign / labelWidth / fieldSize / fieldVariant. |
ViewMode | 'edit' | 'preview' | 'code'. |
PropertyType | 'string' | 'number' | 'boolean' | 'select' — the control kind of a PropertySchema. |
FieldTypeConfig | One entry of FIELD_TYPE_CONFIGS. |