Field model
Field configuration
Use these config keys with the source files and examples that read them.
text, email, password, url, tel, textarea, number
select, radio, checkbox, switch, date, slider, rating
phone, file, array, object, multiSelect, combobox, autocomplete
multiCombobox, color, duration, location, masked
aliases: multiselect, multicombobox, colorPicker, maskedInput
Field configuration
FormedibleFieldConfig defines the public keys. normalizeFieldConfig fills runtime defaults before the registry chooses a renderer.
- Source: packages/formedible/src/lib/formedible/types.ts lines 243-313 define the top-level field keys and nested config objects.
- Renderer lookup: packages/formedible/src/components/formedible/fields/field-registry.tsx maps normalized field types to components.
- Alias behavior is tested in tests/formedible/advanced-fields.test.tsx under “legacy alias fields render field-specific compatibility UI”.
| Property | Type | Default | Description |
|---|---|---|---|
name | string | required | TanStack Form field path. Nested paths are joined by the renderer; see field-path.ts and object-field.tsx. |
type | FormedibleFieldType | text | Renderer key. normalize-field-config.ts maps multiselect, multicombobox, colorPicker, and maskedInput aliases before registry lookup. |
label | ReactNode | — | Rendered by field-wrapper.tsx. Dynamic text tokens are resolved in use-formedible.tsx. |
description | ReactNode | — | Field help copy rendered below the label by field-wrapper.tsx, separate from validation errors. |
placeholder | string | — | Passed to renderers that read a placeholder: text, textarea, number, password, phone, autocomplete, combobox-style fields, and primitive array items. Tokens resolve in use-formedible.tsx. |
dynamicPlaceholder | boolean | — | Declared on the type for compatibility. Current dynamic placeholder behavior comes from token resolution on placeholder. |
disabled | boolean | false | Set per field or through the form-level disabled option; normalization fills the default. |
required | boolean | false | Marks the label/control as required and also feeds Formedible’s built-in required-value validator before schema, validation, inlineValidation, asyncValidation, min, max, and related validators. |
className / inputClassName | string | — | className styles the field shell; inputClassName is passed by input renderers that expose an inner control class. |
page / tab / section | number / string / FormedibleFieldSection | — | Layout metadata used by use-multi-page.ts, use-form-tabs.ts, and section rendering in use-formedible.tsx. |
conditional | string | (values) => boolean | — | String paths are truthy checks; functions receive current form values or local object-array item values. |
options | FormedibleFieldOption[] | (values) => FormedibleFieldOption[] | [] | Used by select, radio, multiSelect, combobox, autocomplete fallback, and multiCombobox through advanced-field-utils.ts. |
optionSets | Record<string, FormedibleFieldOption[]> | — | Typed compatibility metadata. Built-in field renderers read options or nested autocompleteConfig.options instead. |
datalist | FormedibleFieldOption[] | — | Native datalist suggestions for text-field.tsx and number-field.tsx. |
arrayConfig / objectConfig / nestedFields | nested config | — | Structural config for array-field.tsx and object-field.tsx. objectConfig.fields wins over nestedFields for object fields. |
min / max / step | number | — | Top-level number bounds used by number-field.tsx and as slider fallbacks. |
rows / maxLength | number | — | Top-level textarea settings. They take priority over textareaConfig.rows and textareaConfig.maxLength. |
mask / maskedInputConfig | FormedibleMaskedInputMask / nested config | — | Consumed by masked-field.tsx. type: "maskedInput" normalizes to masked before lookup. |
textareaConfig / passwordConfig / numberConfig | nested config | — | Consumed by textarea-field.tsx, password-field.tsx, and number-field.tsx. Top-level rows, maxLength, min, max, and step take priority where supported. |
dateConfig / sliderConfig / ratingConfig / fileConfig | nested config | — | Advanced renderer config defined in types.ts and consumed by the matching field component. |
multiSelectConfig / comboboxConfig / autocompleteConfig / multiComboboxConfig | nested config | — | Search, creation, max-selection, and autocomplete settings consumed by the matching option renderers. |
colorConfig / phoneConfig / durationConfig / locationConfig | nested config | — | Advanced renderer config defined in types.ts and consumed by the matching field component. |
help | ReactNode | FormedibleHelpConfig | — | Supplementary help rendered by field-wrapper.tsx when present. |
validation / inlineValidation | validator config | — | Field-level validation inputs read by buildFieldValidators. |
emailConfig | never | unsupported | Intentionally unsupported; use schema or validation for email-specific rules. |
component / wrapper | FormedibleFieldComponent / FormedibleFieldWrapper | — | Per-field render escape hatches used by field-renderer.tsx before registry fallback and globalWrapper. |
custom props | unknown | — | Retained for custom renderers and metadata. Built-in behavior should use supported config keys above. |
Text inputs
Text-like fields share the same base config. Add renderer-specific config only when the field needs it.
- Text, email, url, and tel share text-field.tsx; datalist renders native suggestions there and in number-field.tsx.
- Textarea config: { textareaConfig: { rows: 4, maxLength: 500, resize: "vertical", showWordCount: true } }. See textarea-field.tsx and tests/formedible/basic-fields.test.tsx.
- Password config: { passwordConfig: { showToggle: true, strengthMeter: true, minStrength: 3 } }. See password-field.tsx.
- Masked config: { type: "masked", mask: "999-99-9999" } or { type: "maskedInput", maskedInputConfig: { mask: "000-00-0000" } }. See masked-field.tsx and tests/formedible/advanced-fields.test.tsx.
Selection fields
Selection fields use options; checkbox and switch use booleans. String options are normalized before render.
- Select config: { name: "role", type: "select", options: [{ value: "qa", label: "QA" }] }. See apps/web/src/components/docs/examples/array-fields-form.tsx.
- Radio config: { name: "destination", type: "radio", options: ["beach", "mountains", "city"] }. See tests/formedible/advanced-fields.test.tsx vacationFlowFields.
- Checkbox and switch render boolean controls from checkbox-field.tsx and switch-field.tsx; tests/formedible/basic-fields.test.tsx covers their primitive imports and default boolean value path.
- Dependent options use a function: options: (values) => values.plan === "team" ? ["audit", "roles"] : ["profile"]. The resolver is resolveFieldOptions in advanced-field-utils.ts.
Advanced inputs
Advanced fields keep settings in nested config objects. Use the config that matches the field type.
- Live example: /docs/examples?example=advanced-fields, id advanced-fields, source apps/web/src/components/docs/examples/advanced-field-types-form.tsx.
- Rating config: { ratingConfig: { max: 5, allowHalf: true, icon: "star", size: "lg", showValue: true } }. Source: rating-field.tsx.
- Slider config: { sliderConfig: { min: 0, max: 100, step: 5, valueLabelSuffix: "%", showValue: true, marks: [{ value: 50, label: "Mid" }] } }. Source: slider-field.tsx and tests/formedible/advanced-fields.test.tsx.
- Color, phone, duration, location, date, and file keys are defined in types.ts lines 640-788 and consumed by their matching field files.
Multi-value fields
multiSelect, combobox, autocomplete, and multiCombobox share the option model. Their nested configs control search, creation, limits, and autocomplete loading.
- multiSelect config: { multiSelectConfig: { searchable: true, creatable: true, maxSelections: 3, placeholder: "Pick skills" } }. Source: multi-select-field.tsx.
- combobox config: { comboboxConfig: { searchable: true, searchPlaceholder: "Search countries", noOptionsText: "No match" } }. Source: combobox-field.tsx.
- autocomplete config: { autocompleteConfig: { options: ["France"], minChars: 1, debounceMs: 300, allowCustom: false } }. Source: autocomplete-field.tsx; stale async behavior is tested in tests/formedible/advanced-fields.test.tsx.
- Example pointers: contact uses combobox and multiCombobox in apps/web/src/components/docs/examples/contact-form.tsx; survey uses multiSelect in apps/web/src/components/docs/examples/survey-form.tsx.
Structural fields
array and object fields render child fields with nested paths. objectConfig.fields wins over nestedFields for object fields.
- Working link: /docs/examples?example=arrays, id arrays, source apps/web/src/components/docs/examples/array-fields-form.tsx.
- Object arrays use { arrayConfig: { itemType: "object", minItems: 1, maxItems: 10, sortable: true, defaultValue: { name: "", email: "" }, objectConfig: { layout: "grid", columns: 2, fields: [{ name: "name", type: "text", label: "Name" }, { name: "email", type: "email", label: "Email" }] } } }. Source: array-field.tsx.
- Scalar arrays use { arrayConfig: { itemType: "email", itemPlaceholder: "contact@company.com", defaultValue: "" } }. itemLabel, addButtonLabel, removeButtonLabel, and itemPlaceholder are consumed by array-field.tsx.
- Object fields use objectConfig.fields first, then nestedFields. Source: object-field.tsx; nested behavior is covered in tests/formedible/nested-fields.test.tsx.
Dynamic behavior
Dynamic behavior lives in field config. Conditions, option functions, and text tokens resolve from current values.
- Visibility config: { conditional: "billingAddress" } checks a path; { conditional: (values) => values.destination === "beach" } runs against current values.
- Dynamic text uses tokens in label, description, placeholder, page title, page description, and section copy. See resolveDynamicText in dynamic-text.ts and use-formedible.tsx.
- Live examples: /docs/examples?example=conditional-pages for page-level conditions and /docs/examples?example=conditional-object-array for conditions inside array object items.
- Tests: tests/formedible/advanced-fields.test.tsx checks dynamic labels, conditional car fields, and page copy; tests/formedible/section-rendering.test.tsx checks section output.
Custom rendering
Custom rendering has a fixed order: field component, defaultComponents, then registry. Field wrapper wraps first, then globalWrapper.
- Per-field component wins first through the component key. Then defaultComponents[renderConfig.type], then getFieldComponent(type) from field-registry.tsx.
- Per-field wrapper wraps a single field; globalWrapper wraps rendered fields from useFormedible options. Both receive fieldConfig, field, and children.
- The shared code card on this page uses docsCodeExamples id field-registry-extension from apps/web/src/features/docs/code-examples.ts.
- The advanced-fields live example shows sliderConfig.visualizationComponent for custom slider visuals without replacing the whole slider renderer.
Customize the copied registry
The registry is copied into your app, so customize supported field types by editing the local mapping directly.
import type { ReactNode } from 'react';
import { NumberField } from '@/components/ui/formedible/fields/number-field';
import { TextField } from '@/components/ui/formedible/fields/text-field';
import type { FormedibleFieldRenderProps, FormedibleFormValues, NormalizedFieldType } from '@/components/ui/formedible/lib/types';
type FieldComponent = <TFormValues extends FormedibleFormValues>(props: FormedibleFieldRenderProps<TFormValues>) => ReactNode;
const fieldRegistry: Partial<Record<NormalizedFieldType, FieldComponent>> = {
number: NumberField,
text: TextField,
};
export function getFieldComponent<TFormValues extends FormedibleFormValues>(
type: NormalizedFieldType,
): (props: FormedibleFieldRenderProps<TFormValues>) => ReactNode {
return fieldRegistry[type] ?? TextField;
}