Skip to content

Field types

24 built-in types, registered by registerBuiltInFields(). Every field also takes the shared base properties — label, required, visibleWhen/disabledWhen, and width — covered once in Base props below, not repeated per type.

Base props

Every field type below accepts these 14 on top of its own — documented once here rather than repeated per type.

NameTypeRequiredDescription
namestringRequired

Field key: the RHF register name, and the identifier that conditions, copyFrom/countryFrom/optionsFrom, and dependsOn target by name. Must not contain a dot (validator-enforced — dots are read as nested paths by RHF and the condition engine).

labelstringOptional

Visible label text.

static and submit ignore it (their own content/text prop is the visible text instead).

descriptionstringOptional

Helper text rendered under the label and wired into aria-describedby alongside any error.

badgestringOptional

Short annotation rendered beside the label — "Required in Germany" — so a conditionally-revealed field can say why it appeared. Part of the field's accessible name, unlike the required mark, which is aria-hidden because the control already exposes required-ness on its own.

needs a label to annotate: no label, no badge — which also means static, submit, and hidden ignore it, since they render none.

autocompletestring (HTML autocomplete value)Optional

The control's autocomplete attribute — "name", "email", "street-address", "postal-code", "address-level2", "bday". WCAG 2.2 SC 1.3.5 (AA) requires one on every field collecting information about the person filling the form; a label alone does not carry the purpose, since "Postleitzahl" and "ZIP" are the same purpose to a person and neither to a parser. Typed as a plain string rather than an enum of the WCAG purposes because the attribute is a grammar around one purpose token — "section-owner-1 name", "shipping street-address", "mobile tel" and "off" are all valid and an allowlist would reject them.

reaches the DOM on text, email, password, textarea, number, masked, time, phone and otp — the types whose control is a native text-entry input. date, select and country render a popover behind a button, and HTML ignores the attribute on file, checkbox/switch, radio and segmented; set there it is inert, not an error. phone and otp already default to "tel" and "one-time-code", which an unset value leaves alone.

placeholderstringOptional

Placeholder/prompt text; meaning is per-control (input placeholder, empty-select prompt, unset-date prompt).

on group it overrides the "Add" button's label instead of a text placeholder; on file it replaces the dropzone's "Drag files here, or browse" prompt, which is part of the file input's accessible name — so it is read aloud after the label, not just shown; has no effect on radio, segmented, checkbox group, rating, slider, or signature.

requiredbooleanOptional

Marks the field mandatory: shows a required mark and drives the generated zod schema.

on rating it also changes clear behavior (clicking the current value clears it only when optional); radio, segmented, and country can never be cleared once set, regardless of required. No effect on static/submit (no user input) or hidden (always has a value).

disabledbooleanOptional

Statically disables the control; disabledWhen/enabledWhen layer on top of (not instead of) this flag.

visibleWhenConditionSpecOptional

Hides the field and excludes it from validation and the submit payload while the spec doesn't match. Value operators only (equals/notEquals/in) — isValid is rejected here, since visibility itself decides which schema gets built.

disabledWhenConditionSpecOptional

Disables the field while the spec matches. The only base prop where isValid is allowed (checking a sibling's own schema validity). Mutually exclusive with enabledWhen (validator-enforced).

enabledWhenConditionSpecOptional

Inverse of disabledWhen — disabled while the spec does NOT match; reads straight for "enabled once X is valid". Mutually exclusive with disabledWhen.

enabledWhenVerifiedstringOptional

Keeps the field disabled until the named sibling otp field's code is verified.

must reference a sibling of type otp; rejected inside a group (validator-enforced).

copyFromstringOptional

Mirrors a same-type sibling's value until the user edits this field, then the source wins again on its next change (same semantics as phone's countryFrom).

not supported on phone, otp, password, file, signature, group, hidden, static, or submit fields (validator-enforced); a select pair must also match multiple, and a date pair must also match range.

widthResponsiveFieldWidthOptional

'full' | 'half' | 'third' | 'quarter', or a per-breakpoint object, on the 12-col grid; unset breakpoints fall back to full rather than cascading from a smaller breakpoint.

Shared shapes

Shapes referenced by name from more than one field type's props below, explained once here.

Option
{ label: string; value: string | number; disabled?: boolean }
One selectable choice. radio/segmented/checkbox compare value with ===; the native <select> compares String(value), so numeric and string values that stringify the same are not distinguished there. disabled greys out just that option.
Used by: select, radio, segmented, checkbox, switch
TextRules
{ minLength?; maxLength?; pattern?: string; message?; trim?; allow?: string; matches?: string; matchesMessage? }
pattern is a string (not a RegExp) so it stays JSON-serializable; trim also normalizes the visible input on blur, not just the parsed payload; allow blocks disallowed characters at typing/paste time (a character-class body, e.g. "A-Za-z "). matches is cross-field equality (confirm password/email): enforced by a form-level refine, never the field's own schema — the isValid oracle behind isValid conditions parses field schemas in isolation and can't see it.
Used by: text, email, textarea, password
PasswordComplexity
{ uppercase?; lowercase?; number?; special?: boolean; minLength?: number }
Each flag adds one line to the live pass/fail checklist rendered under the password input; minLength adds its own line instead of folding into rules.minLength.
Used by: password
optionsFrom
{ field: string; map: Record<string, Option[]> }
field must be a sibling single-value select or country field (validator-enforced — a multi-select or a dynamic optionsFrom source is rejected); map is keyed by String(source value). A source value with no matching key renders an empty, disabled select (dev-only console warning, not a hard error). Not supported inside groups.
Used by: select
countryFrom
string
Names a sibling country or single-value select field (whose option values are ISO alpha-2 — a country field qualifies by construction, a select's options are validated as ISO codes). The phone field re-syncs its calling code from that source on every change — the source always wins over a manual pick, until the source changes again. Rejected inside groups; a cross-step pairing only dev-warns (source edits made while the phone field is unmounted are skipped until it remounts).
Used by: phone
Sibling-bound min/max (date & time)
minDateField/maxDateField (date), minTimeField/maxTimeField (time): string
Bounds a field against a sibling date/time field's CURRENT value (e.g. "end date on/after start date") via a form-level superRefine — never the field's own schema, so the isValid oracle (which parses fields in isolation) can't see it. minDateField/maxDateField are rejected together with range: true on the date field itself (validator-enforced); the time equivalents have no such restriction.
Used by: date, time

Text

Single-line text input.

NameTypeRequiredDescription
rulesTextRulesOptional

Length/pattern/trim/allow/matches constraints — see the shared TextRules shape below.

Value: string

{
  "type": "text",
  "name": "fullName",
  "label": "Full name",
  "required": true
}

Email

Text input with email-format validation.

NameTypeRequiredDescription
rulesTextRulesOptional

Same TextRules shape as text — the email-format check itself is built in, not part of rules.

Value: string (email-format checked on submit, not blocked while typing)

{
  "type": "email",
  "name": "email",
  "label": "Email address",
  "required": true
}

Password

Text input with a show/hide toggle.

Optional complexity rules (min length, upper/lower/digit/symbol requirements) via complexity.

NameTypeRequiredDescription
rulesTextRulesOptional

Same TextRules shape as text/email — length/pattern/trim/allow/matches, layered on top of complexity.

complexityPasswordComplexityOptional

Adds a live pass/fail checklist (uppercase/lowercase/number/special/minLength) under the input while typing; only the currently-failing rules render, and the checklist replaces (never duplicates) the schema's error text.

Value: string

{
  "type": "password",
  "name": "password",
  "label": "Password",
  "required": true,
  "complexity": {
    "minLength": 8,
    "number": true
  }
}

Textarea

Multi-line text input.

NameTypeRequiredDescription
rulesTextRulesOptional

Same TextRules shape as text — see the shared TextRules shape below.

Value: string

{
  "type": "textarea",
  "name": "bio",
  "label": "Bio",
  "rules": {
    "maxLength": 500
  }
}

Masked

Pattern-masked text input (# digit, A letter, * alphanumeric, other characters literal).

Stores the raw token characters — the mask is presentation-only, not part of the value.

NameTypeRequiredDescription
maskstringRequired

Pattern made of '#' (digit), 'A' (letter), '*' (alphanumeric) token chars — every other character is a literal; must contain at least one token char (validator-enforced).

messagestringOptional

Custom error for an incomplete value; defaults to the "Incomplete value" message.

Value: string — RAW token characters only (e.g. "4111111111111111"), never the punctuated display string

{
  "type": "masked",
  "name": "ssn",
  "label": "SSN",
  "mask": "###-##-####"
}

Number

Numeric input with optional min, max, and step.

NameTypeRequiredDescription
minnumberOptional

Native min constraint.

maxnumberOptional

Native max constraint.

stepnumberOptional

Native step constraint (also the spinner increment).

Value: number, or undefined once the input is blank/invalid

{
  "type": "number",
  "name": "age",
  "label": "Age",
  "min": 0,
  "max": 120,
  "step": 1
}

OTP

One-time-passcode input (shadcn input-otp) with a configurable length.

dependsOn gates it on a sibling field's value and invalidates the verified code when that value changes.

NameTypeRequiredDescription
lengthnumberRequired

Number of code digits/characters rendered as input-otp slots.

dependsOnstringOptional

Sibling field whose value gates this code: changing it invalidates any already-verified code (generation-stamped) and resets the send/verify flow. Rejected inside groups.

Value: string of exactly length characters; submit gating reads the separate verified-code registry, not this string alone

{
  "type": "otp",
  "name": "emailOtp",
  "label": "Verification code",
  "length": 6,
  "dependsOn": "email"
}

Phone

International phone input (react-phone-number-input) with a country flag dropdown.

countryFrom syncs the selected country from a sibling country or single-select field — the source always wins over a manual pick until it changes again.

NameTypeRequiredDescription
defaultCountrystring (ISO alpha-2)Optional

Initial calling code shown before the user picks or types a number.

preferredCountriesstring[] (ISO alpha-2)Optional

Pins these countries, in this order, above the rest (which stay name-sorted) in the flag dropdown.

countryFromstringOptional

Sibling country or single-value select field this phone re-syncs its calling code from on every change — see the shared countryFrom shape below.

Value: string — international phone string (e.g. "+15551234567"), or ""

{
  "type": "phone",
  "name": "phone",
  "label": "Phone number",
  "defaultCountry": "US",
  "preferredCountries": [
    "US",
    "CA",
    "GB"
  ],
  "countryFrom": "country"
}

Select

Dropdown, or a searchable combobox when searchable is set; supports multiple.

optionsFrom can derive the option list from another field's current value instead of a static options array.

NameTypeRequiredDescription
optionsOption[]Optional

Static option list. Exactly one of options or optionsFrom is required — set neither or both and validateFormConfig throws.

optionsFrom{ field: string; map: Record<string, Option[]> }Optional

Derives the option list from a sibling field's current value instead of a static array — see the shared optionsFrom shape below.

searchablebooleanOptional

Renders a filterable combobox instead of the native <select>.

multiplebooleanOptional

Value becomes Option["value"][] instead of a single value; also switches rendering to the combobox (like searchable).

Value: single (default): the matching Option["value"] (string | number), or undefined. multiple: true → Option["value"][]

{
  "type": "select",
  "name": "role",
  "label": "Role",
  "options": [
    {
      "label": "Admin",
      "value": "admin"
    },
    {
      "label": "Member",
      "value": "member"
    }
  ]
}

Country

Searchable combobox of ISO 3166-1 alpha-2 countries, with flags.

Value is ISO alpha-2 by construction, so it's always a valid phone countryFrom source. Like an optional radio, it can't be cleared once set — the combobox has no clear row.

NameTypeRequiredDescription
countriesstring[] (ISO alpha-2)Optional

Restricts the combobox to this subset; defaults to every ISO 3166-1 alpha-2 country (libphonenumber-js getCountries()).

preferredCountriesstring[] (ISO alpha-2)Optional

Pins these countries, in this order, above the rest; must be a subset of countries when both are set (validator-enforced).

Value: string — ISO 3166-1 alpha-2 code (e.g. "AE"), or undefined

{
  "type": "country",
  "name": "country",
  "label": "Country",
  "preferredCountries": [
    "US",
    "CA",
    "GB"
  ]
}

Radio

Exclusive choice from a set of options.

NameTypeRequiredDescription
optionsOption[]Required

Choices rendered as a radix RadioGroup; at least one option is required (validator-enforced).

Value: the matching Option["value"]

{
  "type": "radio",
  "name": "plan",
  "label": "Plan",
  "options": [
    {
      "label": "Monthly",
      "value": "monthly"
    },
    {
      "label": "Yearly",
      "value": "yearly"
    }
  ]
}

Segmented

Radio semantics (radix RadioGroup) rendered as a joined button group.

An optional segmented field can't be cleared once set — same as radio.

NameTypeRequiredDescription
optionsOption[]Required

Choices rendered as a joined button group with radio semantics (radix RadioGroup, not a toggle group) — guaranteed radiogroup/radio roles and arrow-key roving focus; at least one option is required.

Value: the matching Option["value"] (radio semantics — same as radio)

{
  "type": "segmented",
  "name": "size",
  "label": "Size",
  "options": [
    {
      "label": "S",
      "value": "s"
    },
    {
      "label": "M",
      "value": "m"
    },
    {
      "label": "L",
      "value": "l"
    }
  ]
}

Checkbox

Boolean checkbox, or a checkbox group when options is set.

NameTypeRequiredDescription
optionsOption[]Optional

Presence of options renders a checkbox GROUP (value becomes Option["value"][] of the checked entries); omit it for a single boolean checkbox.

Value: no options (default): boolean. options set → Option["value"][] of the checked entries

{
  "type": "checkbox",
  "name": "acceptTerms",
  "label": "I accept the terms",
  "required": true
}

Switch

Boolean toggle switch.

Always a single boolean value — options are not supported (use checkbox for a group).

NameTypeRequiredDescription
optionsOption[]Optional

Inherited from the shared checkbox/switch config shape but NOT functionally supported on switch (known issue) — a switch always renders and stores a single boolean regardless of options; use checkbox for a group.

Value: boolean — always a single toggle; options has no effect

{
  "type": "switch",
  "name": "marketingOptIn",
  "label": "Send me updates"
}

Date

Single date or range picker (react-day-picker).

Value is a plain "yyyy-MM-dd" string (or a {from, to} pair for range), compared by date part — never epoch math.

NameTypeRequiredDescription
rangebooleanOptional

Renders a range calendar and switches the value to { from?; to? } instead of a single "yyyy-MM-dd" string.

minDate"yyyy-MM-dd"Optional

Earliest selectable date; also biases the calendar's initial month.

maxDate"yyyy-MM-dd"Optional

Latest selectable date; also biases the calendar's initial month.

pickerBounds"restrict" | "validate"Optional

What minDate/maxDate do to the calendar — they always constrain the value. "restrict" (default) disables out-of-range days and clamps month navigation, for a range that is genuinely unavailable. "validate" leaves those days selectable by mouse and keyboard so picking one fails with message instead, which is how an age cutoff says "You must be 18 or older" rather than greying out most of the calendar. Governs these two bounds only — minDateField/maxDateField never shape the calendar either way.

minDateFieldstringOptional

Bounds this date to be on/after a sibling non-range date field's current value (form-level refine, not this field's own schema); rejected together with range: true (validator-enforced).

maxDateFieldstringOptional

Bounds this date to be on/before a sibling non-range date field's current value; same range: true restriction as minDateField.

messagestringOptional

Custom error for a minDate/maxDate violation, replacing "Must be at least/at most <date>" — the way to say "You must be 18 or older" instead of a bare bound. One sentence covers both bounds, and with range: true both endpoints; unparseable values and the minDateField/maxDateField rules keep their own messages.

Value: single (default): "yyyy-MM-dd" string, or undefined. range: true → { from?: "yyyy-MM-dd"; to?: "yyyy-MM-dd" } (from may be set alone mid-selection)

{
  "type": "date",
  "name": "birthDate",
  "label": "Date of birth",
  "maxDate": "2026-07-12"
}

Time

Native time input.

Value is a zero-padded "HH:mm" string, compared lexicographically — same convention as dates.

NameTypeRequiredDescription
minTime"HH:mm"Optional

Earliest selectable time — zero-padded 24h, compared lexicographically.

maxTime"HH:mm"Optional

Latest selectable time; must not be before minTime (validator-enforced).

stepMinutesnumberOptional

Minute increment; converted to seconds for the native input's step attribute.

minTimeFieldstringOptional

Bounds this time to be on/after a sibling time field's current value (form-level refine).

maxTimeFieldstringOptional

Bounds this time to be on/before a sibling time field's current value (form-level refine).

Value: zero-padded "HH:mm" string, or ""

{
  "type": "time",
  "name": "appointmentTime",
  "label": "Appointment time",
  "minTime": "09:00",
  "maxTime": "17:00",
  "stepMinutes": 15
}

Rating

1..max star rating (default max 5).

Clicking the current value clears it when the field is optional.

NameTypeRequiredDescription
maxnumberOptional

Highest star value (1..max); defaults to 5 when unset. Must be between 2 and 10 (validator-enforced).

Value: number 1..max, or undefined (clicking the current value clears it when the field is optional)

{
  "type": "rating",
  "name": "satisfaction",
  "label": "How satisfied are you?",
  "max": 5
}

Slider

Numeric slider (shadcn Slider) with min, max, and step.

NameTypeRequiredDescription
minnumberRequired

Lower bound of the range.

maxnumberRequired

Upper bound of the range.

stepnumberOptional

Increment between selectable values.

Value: number

{
  "type": "slider",
  "name": "volume",
  "label": "Volume",
  "min": 0,
  "max": 100,
  "step": 5
}

Signature

Draw-to-sign canvas (signature_pad); value is a PNG data URL.

Not keyboard-accessible — drawing is inherently pointer/touch-only, with no keyboard fallback.

NameTypeRequiredDescription
penColorstring (CSS color)Optional

Ink color; defaults to signature_pad's own default (black). Treated as static config — changing it at runtime recreates the pad and only restores the value captured at mount.

heightPxnumberOptional

Canvas height in pixels; defaults to 160.

Value: PNG data URL string, or "" until the user signs

{
  "type": "signature",
  "name": "signature",
  "label": "Signature",
  "heightPx": 180
}

File

Drag-and-drop or browse, with a per-file rejection reason; accept, maxSizeMB, and multiple.

The field holds File objects and stops there — uploading them is the host's job, in onSubmit. There is no progress bar because the engine never transfers anything.

NameTypeRequiredDescription
acceptstringOptional

Native accept attribute (MIME types / extensions), e.g. ".pdf,.doc,.docx". Also enforced by the generated schema — a file the browser could not type matches no MIME token, so list the extension too (".pdf,application/pdf") if those must get through — and written out as the dropzone's hint ("PDF, DOC or DOCX").

maxSizeMBnumberOptional

Rejects files larger than this via the generated schema (a field error, not a manual setError, so it survives the next resolver run); unset means no size limit.

multiplebooleanOptional

Value becomes File[] instead of a single File; also allows picking more than one file at once.

Value: single (default): a File, or undefined — not JSON-serializable, consumers handle upload in onSubmit. multiple: true → File[]. Files that failed accept or maxSizeMB are in there too: they stay in the value so the field can show each one its own reason, and the field is invalid until they are removed — so onSubmit never sees them, but a manual read of the value will.

{
  "type": "file",
  "name": "resume",
  "label": "Resume",
  "accept": ".pdf,.doc,.docx",
  "maxSizeMB": 5
}

Hidden

Not rendered; carries a static value along with the rest of the form's values.

NameTypeRequiredDescription
valueunknownRequired

The static value carried through with the rest of the form's values; must not appear in a step's fieldNames (validator-enforced — it's exempt from step assignment).

Value: config.value verbatim (unknown) — carried through untouched

{
  "type": "hidden",
  "name": "referralSource",
  "value": "landing-page"
}

Static text

Non-field content block — heading, paragraph, or divider. Has no value.

NameTypeRequiredDescription
contentstringRequired

The block's text; may contain an allowlisted inline <a href> or <br> (safe schemes only) via the field's rich-text renderer.

as"h1" | "h2" | "p" | "divider"Optional

Element to render as; omit for a plain paragraph. "divider" ignores content and renders a separator instead.

Value: no value — not a form field; renders content only

{
  "type": "static",
  "name": "sectionIntro",
  "as": "h2",
  "content": "Contact details"
}

Group (repeatable)

Repeatable sub-array of fields (e.g. "add another team member"), with min/max.

Known v1 limitation: visibleWhen/disabledWhen conditions on fields nested inside a group are not skipped by validation.

NameTypeRequiredDescription
fieldsAnyFieldConfig[]Required

Recursive — any field type documented on this page (not expanded here to avoid infinite recursion in this table). Known v1 limitation: visibleWhen/disabledWhen conditions on fields nested inside a group are NOT skipped by validation.

minnumberOptional

Minimum rows; defaults to 0. The remove button disables once row count reaches this floor.

maxnumberOptional

Maximum rows; defaults to unbounded. The add button disables once row count reaches this ceiling.

Value: an array of row objects, one per repetition, each shaped by fields (bounded by min/max)

{
  "type": "group",
  "name": "teamMembers",
  "label": "Team members",
  "min": 1,
  "max": 5,
  "fields": [
    {
      "type": "text",
      "name": "name",
      "label": "Name",
      "required": true
    }
  ]
}

Submit button

Submit button with configurable text and variant.

NameTypeRequiredDescription
textstringRequired

Button label.

variantButtonVariantOptional

'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link' (shadcn Button variant).

Value: no value — renders a submit button

{
  "type": "submit",
  "name": "submit",
  "text": "Create account",
  "variant": "default"
}

For the exact per-type config shape (which properties each type accepts), see the FieldConfig union in form-builder/core/types.ts, or a working config for several of these under Examples.