Field
View as MarkdownA form field wrapper that provides accessible labeling, helper and error text, validation state and indicators for form inputs.
Features
Permalink to heading "Features"- Automatic label association with form controls
- Helper text and error text, linked to the control with the right ARIA attributes
- Reads the browser's own constraint validation:
required,pattern,type="email",min/max,minlength/maxlengthand more decide whether a field is valid, and error texts can be narrowed to the constraint that failed - Custom validation per field, synchronous or asynchronous, next to the schema or callback validation of the Form - all of them end up in the same place
- Validation modes decide when errors become visible, and an error that is showing always follows the user while they fix it
- State flags for touched, dirty, filled, focused, valid, invalid, disabled, required and read-only, mirrored as data attributes on every part
- Indicators for the required, invalid, valid and validating state
- Disabled by a surrounding Fieldset or
<fieldset disabled> - Works with all form-related primitives (Checkbox, Select, RadioGroup, etc.) and native inputs
<primitives:field.root>
<primitives:field.label />
<primitives:field.control asChild="{true}">
<!-- Your form input here -->
</primitives:field.control>
<primitives:field.indicator type="required" />
<primitives:field.helperText />
<primitives:field.errorText />
</primitives:field.root>
Installation
Permalink to heading "Installation"typo3 ui:add field
Please copy the files manually from GitHub into your project.
Read more about installing Components and Primitives.
Examples
Permalink to heading "Examples"Required Field
Permalink to heading "Required Field"Mark a field as required. An empty required field stays quiet until the user has edited it or the form is submitted.
With Helper Text
Permalink to heading "With Helper Text"Add helpful text below the input. It is linked to the control with aria-describedby.
Invalid State
Permalink to heading "Invalid State"Indicate that the field has an error and display an error message.
Disabled Field
Permalink to heading "Disabled Field"Disable the entire field.
With Checkbox
Permalink to heading "With Checkbox"Use with the Checkbox component.
Native Validation
Permalink to heading "Native Validation"The field reads the ValidityState of its control, so the constraints you already know are validation rules: required (the prop of the field, not an attribute), type="email" and the like on the input, and pattern, min, max, step, minlength and maxlength, which you pass to the input part as plain attributes.
The form renders novalidate, so the browser never shows its own bubble. Instead, an error text without a match shows the message of the failed constraint, in the language of the browser. Give it a match to show your own text for one constraint, e.g. valueMissing, typeMismatch, patternMismatch, tooShort, tooLong, rangeUnderflow, rangeOverflow, stepMismatch, badInput or customError. An error text with a match stays hidden until exactly that constraint failed.
A field validates the value of the primitive inside it, not only native inputs: a required Select, Combobox, Checkbox, RadioGroup or CheckboxGroup counts as missing while nothing is chosen. The other constraints come from the native control, so they apply to inputs and textareas.
Validation Mode
Permalink to heading "Validation Mode"validationMode decides when a field validates and shows its errors:
onBlur(the default) validates when the user leaves a field they have edited. Tabbing through untouched fields stays quiet.onSubmitshows errors after the first submit attempt, and from then on as the user types.onChangevalidates on every change.
In every mode, an error that is already showing is validated again on every change, so the message goes away as soon as the user has fixed the value, instead of waiting for the next blur. A submit validates all fields, shows every error and moves the focus to the first invalid one.
Error texts and indicators are never shown or hidden while a mouse button or a finger is down. A field blurs on the press that leaves it, and an error text appearing at that moment would move the button the user is about to release on, so the click would be lost. They wait for the release instead (keyboard users get them right away).
Indicators
Permalink to heading "Indicators"An indicator shows its content while the field is in one state, and stays hidden otherwise. Its type is one of required, invalid, valid and validating. valid appears once the field has been validated and passed.
Async Validation
Permalink to heading "Async Validation"A field can run its own check, for example to ask the server whether a username is still free. Pass a validate function that returns a message (or a list of messages) to mark the field invalid, and null to accept the value. It may return a promise.
A function cannot travel through the server-rendered markup, so pass it from TypeScript: render the field with autoMount="{false}", and mount it yourself. While the promise is pending the field is validating (use the validating indicator), a result for a value the user has changed meanwhile is dropped, and a submit waits for it.
validate receives the value and a snapshot of the native validity. It runs at the same points that make an error visible (see the validation mode), not on every keystroke, so it is a good place for a request.
Use updateProps({ validate }) to replace the function later. The schema or callback of the Form and the errors the server sent back (422 or from onSubmit) do not need any of this, they reach the field on their own and are shown in the same error text.
Linked Fields
Permalink to heading "Linked Fields"A field can react to a different field's value changing via the listenTo prop - names of sibling fields within the same form to watch. Two things happen whenever a listened-to field changes its value: a fluid-primitives:field:dependencychange event is dispatched on the root element of this field, for any imperative DOM reaction you need, and this field is treated as if its own value had changed. It validates again under the same rules as when its own user types: immediately if it shows an error (or its validationMode is onChange, or the form was submitted before), otherwise at its next blur or submit.
FieldHandle (what both form.api.getField(name) and a Field instance's own .api return) exposes addDependencyChangeListener(callback) for the first half - it registers callback and returns a function that removes it again, the same "call it to start listening, get a cleanup function back" shape as addEventListener/Machine.subscribe, rather than a config-style onX prop you'd set once:
const needsStudentId = (values: FormValues) => values.get('ticketType') === 'student';
const studentIdField = form.api.getField('studentId')!;
const applyStudentIdVisibility = (show: boolean) => {
studentIdField.getRootEl()!.hidden = !show;
studentIdField.setDisabled(!show);
};
// the same rule decides the starting state and every later change
applyStudentIdVisibility(needsStudentId(form.api.getValues()));
studentIdField.addDependencyChangeListener(({ values }) => {
applyStudentIdVisibility(needsStudentId(values));
});
The callback receives dependencies (the current value of each listened-to field, keyed by name) and values, the current FormValues of the whole form, which is what a rule shared with your validation callback usually wants.
See the Complete Example: Event Registration in the Forms guide for this exact pattern used to show/hide a studentId field based on ticketType, in place of recomputing it inside the form's own render callback on every field change.
The classic case is a password confirmation field - passwordConfirm listens to password, so changing password validates passwordConfirm again, instead of leaving a stale result:
<ui:field.root name="password">
<ui:input.root type="password">
<ui:input.label>Password</ui:input.label>
<ui:input.input />
</ui:input.root>
<ui:field.errorText />
</ui:field.root>
<ui:field.root name="passwordConfirm" listenTo="{0: 'password'}">
<ui:input.root type="password">
<ui:input.label>Confirm password</ui:input.label>
<ui:input.input />
</ui:input.root>
<ui:field.errorText />
</ui:field.root>
No consumer JavaScript is needed for the re-triggering itself - listenTo handles that. You still need to decide what "valid" means, though, and a manual validation callback (see Manual Client-Side Validation) fits better here than a single schema: checking each field independently means passwordConfirm's mismatch error can appear even while password hasn't yet satisfied its own length requirement, whereas a single Zod schema with .refine() would silently skip that cross-field check until the base shape (including password's own min(8)) validates first:
validation: ({ values }) => {
const errors: Record<string, { messages: string[] }> = {};
const password = values.get('password');
if (typeof password !== 'string' || password.length < 8) {
errors.password = { messages: ['Password must be at least 8 characters'] };
}
const passwordConfirm = values.get('passwordConfirm');
if (passwordConfirm !== null && passwordConfirm !== password) {
errors.passwordConfirm = { messages: ['Passwords do not match'] };
}
return errors;
},
listenTo accepts more than one name, e.g. listenTo="{0: 'a', 1: 'b'}" - the dependencychange event's dependencies object then carries every listened field's current value, keyed by name.
State and Data Attributes
Permalink to heading "State and Data Attributes"Every part of a field renders the state of the field as data attributes, so a label, an indicator or an error text can be styled without any JavaScript. The server renders the ones that follow from the props (data-disabled, data-invalid, data-required, data-readonly), the others are set once the field is hydrated. The API reference lists them per part.
The same flags are available from TypeScript on field.api (and on the FieldHandle of form.api.getField(name)): touched, dirty, filled, focused, valid, invalid, validating, disabled, required, readOnly and the errors list, together with validate(), clearErrors() and reset(). A fluid-primitives:field:valuechange event bubbles from the root of the field whenever its value settled on a new one, 100 ms after the last keystroke by default.
API Reference
Permalink to heading "API Reference"The following tables cover the available props of the Fluid Primitives.
field.root
Permalink to heading "field.root"Provides shared field state for labels, helper text, errors, indicators and controls. Renders a <div> element.
| Name | Description | Required | Default |
|---|---|---|---|
name | stringThe unique field name used to associate labels, helper text, errors, and form values. | Yes | - |
disabled | booleanWhether the field is disabled. A field inside a disabled `<fieldset>` is disabled as well. | No | - |
invalid | booleanWhether the field is invalid, regardless of validation. Use it for errors the browser's constraint validation and the form's validation cannot compute, e.g. a pre-populated server error. | No | - |
required | booleanWhether the field is required. An empty required field stays quiet until the user edits it or the form is submitted. | No | - |
readOnly | booleanWhether the field is read-only. | No | - |
defaultValue | mixedThe initial value that child controls can use when rendered inside the field. | No | - |
validationMode | Enum\ValidationModeWhen errors become visible. `onBlur` validates on blur once the value was edited, `onSubmit` after the first submit attempt, `onChange` on every change. An error that is showing always revalidates on change, in every mode. | No | OnBlur |
listenTo | string[]Names of sibling fields within the same form whose value changes this field should react to. Dispatches a `fluid-primitives:field:dependencychange` event on this field's root element (see `addDependencyChangeListener()`) on every value change of a listened-to field, and treats that like a change of this field itself, so it revalidates under the same rules as when its own value changes - e.g. a `passwordConfirm` field listening to `password`. | No | - |
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
rootId | stringThe root ID of the component, used for hydration and identification. | No | - |
ids | arrayThe IDs of of the component parts for composition. | No | [] |
autoMount | booleanWhether the client initializes the component on its own. Set it to false to mount it yourself with `mount()` | No | true |
class | stringThe CSS class(es) to be applied to the component. | No | - |
Rendered data attributes
| Attribute | Description |
|---|---|
data-disabled | Present when the field is disabled, or it is inside a disabled fieldset. |
data-invalid | Present when the field is invalid: a native constraint failed, or `validate`, the form or the server reported an error. |
data-valid | Present when the field has been validated and passed. |
data-required | Present when the field is required. |
data-readonly | Present when the field is read-only. |
data-touched | Present once the user has left the field at least once. |
data-dirty | Present when the value differs from the one the field started with. It goes away again when the user reverts the edit. |
data-filled | Present when the field has a value. |
data-focus | Present when focus is inside the field. |
data-name | The name of the field. |
field.label
Permalink to heading "field.label"Labels the associated form control. Renders a <label> element.
| Name | Description | Required | Default |
|---|---|---|---|
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
Rendered data attributes
| Attribute | Description |
|---|---|
data-disabled | Present when the field is disabled, or it is inside a disabled fieldset. |
data-invalid | Present when the field is invalid: a native constraint failed, or `validate`, the form or the server reported an error. |
data-valid | Present when the field has been validated and passed. |
data-required | Present when the field is required. |
data-readonly | Present when the field is read-only. |
data-touched | Present once the user has left the field at least once. |
data-dirty | Present when the value differs from the one the field started with. It goes away again when the user reverts the edit. |
data-filled | Present when the field has a value. |
data-focus | Present when focus is inside the field. |
field.control
Permalink to heading "field.control"Wraps the slotted form control and wires up shared field attributes. Renders the element defined by the asChild prop.
| Name | Description | Required | Default |
|---|---|---|---|
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
Rendered data attributes
| Attribute | Description |
|---|---|
data-disabled | Present when the field is disabled, or it is inside a disabled fieldset. |
data-invalid | Present when the field is invalid: a native constraint failed, or `validate`, the form or the server reported an error. |
data-valid | Present when the field has been validated and passed. |
data-required | Present when the field is required. |
data-readonly | Present when the field is read-only. |
data-touched | Present once the user has left the field at least once. |
data-dirty | Present when the value differs from the one the field started with. It goes away again when the user reverts the edit. |
data-filled | Present when the field has a value. |
data-focus | Present when focus is inside the field. |
field.helperText
Permalink to heading "field.helperText"Displays help or supporting text for the field. Renders a <div> element.
| Name | Description | Required | Default |
|---|---|---|---|
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
Rendered data attributes
| Attribute | Description |
|---|---|
data-disabled | Present when the field is disabled, or it is inside a disabled fieldset. |
data-invalid | Present when the field is invalid: a native constraint failed, or `validate`, the form or the server reported an error. |
data-valid | Present when the field has been validated and passed. |
data-required | Present when the field is required. |
data-readonly | Present when the field is read-only. |
data-touched | Present once the user has left the field at least once. |
data-dirty | Present when the value differs from the one the field started with. It goes away again when the user reverts the edit. |
data-filled | Present when the field has a value. |
data-focus | Present when focus is inside the field. |
field.errorText
Permalink to heading "field.errorText"Displays the validation message of the field, or the text of its match when one constraint failed. Renders a <div> element.
| Name | Description | Required | Default |
|---|---|---|---|
match | Enum\ValidityMatchNarrows the error text to one failed native constraint, e.g. `valueMissing` or `typeMismatch`. Without it the text shows whenever the field is invalid and displays the error messages; with it the slotted text is shown as is. | No | - |
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
Rendered data attributes
| Attribute | Description |
|---|---|
data-disabled | Present when the field is disabled, or it is inside a disabled fieldset. |
data-invalid | Present when the field is invalid: a native constraint failed, or `validate`, the form or the server reported an error. |
data-valid | Present when the field has been validated and passed. |
data-required | Present when the field is required. |
data-readonly | Present when the field is read-only. |
data-touched | Present once the user has left the field at least once. |
data-dirty | Present when the value differs from the one the field started with. It goes away again when the user reverts the edit. |
data-filled | Present when the field has a value. |
data-focus | Present when focus is inside the field. |
field.indicator
Permalink to heading "field.indicator"Shows its content while the field is in the state given by type: required, invalid, valid or validating. Renders a <span> element.
| Name | Description | Required | Default |
|---|---|---|---|
type | Enum\FieldIndicatorTypeThe state the indicator reflects: `required`, `invalid`, `valid` or `validating`. It stays in the DOM and is hidden unless the state matches, so CSS transitions survive. | Yes | - |
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
Rendered data attributes
| Attribute | Description |
|---|---|
data-disabled | Present when the field is disabled, or it is inside a disabled fieldset. |
data-invalid | Present when the field is invalid: a native constraint failed, or `validate`, the form or the server reported an error. |
data-valid | Present when the field has been validated and passed. |
data-required | Present when the field is required. |
data-readonly | Present when the field is read-only. |
data-touched | Present once the user has left the field at least once. |
data-dirty | Present when the value differs from the one the field started with. It goes away again when the user reverts the edit. |
data-filled | Present when the field has a value. |
data-focus | Present when focus is inside the field. |
data-type | The state the indicator reflects. One of: "required", "invalid", "valid", "validating". |
Machine options
Permalink to heading "Machine options"| Name | Description | Default |
|---|---|---|
name | stringThe name of the field. It keys the field in its form and is what the field's value is read by. | - |
defaultValue | unknown | undefinedThe initial value, used while the DOM holds no control for this name yet. | - |
listenTo | string[] | undefinedNames of sibling fields in the same form whose value changes this field reacts to. | - |
ids | ElementIds | undefinedThe ids of the elements. Useful for composition. | - |
disabled | boolean | undefinedWhether the field is disabled. Also inherited from a surrounding native <fieldset disabled>. | |
invalid | boolean | undefinedWhether the field is invalid, regardless of computed validity. Use for app-level errors (e.g. server errors) the machine cannot compute itself. | - |
dirty | boolean | undefinedWhether the field's value has been changed from its initial value. Useful when the field state is controlled by an external library. | - |
touched | boolean | undefinedWhether the field has been touched. Useful when the field state is controlled by an external library. | - |
readOnly | boolean | undefinedWhether the field is read-only. | |
required | boolean | undefinedWhether the field is required. | |
validate | ((details: ValidateDetails) => ValidateResult | Promise<ValidateResult>) | undefinedCustom validation, run against the value and the native validity snapshot. May return a promise; a newer validation run supersedes an in-flight one. | - |
validationMode | ValidationMode | undefinedWhen validation results become visible. | "onBlur" |
onValidityChange | ((details: ValidityChangeDetails) => void) | undefinedCalled when the committed validity changes. | - |
dir | "ltr" | "rtl" | undefinedThe document's text/writing direction. | "ltr" |
id | stringThe unique identifier of the machine. | - |
getRootNode | (() => ShadowRoot | Document | Node) | undefinedA root node to correctly resolve document in custom environments. E.x.: Iframes, Electron. | - |
Machine JavaScript API
Permalink to heading "Machine JavaScript API"| Name | Type | Description |
|---|---|---|
ids | ResolvedElementIds | The resolved element ids, for composition with other components. |
disabled | boolean | Whether the field is disabled (including inherited fieldset disabled state). |
invalid | boolean | Whether the field is invalid. |
valid | boolean | null | Committed validity: null until validation has run. |
required | boolean | Whether the field is required. |
readOnly | boolean | Whether the field is read-only. |
focused | boolean | Whether the control is focused. |
touched | boolean | Whether the control has been blurred at least once. |
dirty | boolean | Whether the value differs from the initial value. |
filled | boolean | Whether the control has a non-empty value. |
validating | boolean | Whether an async validation is in flight. |
errors | string[] | The committed error messages. |
validity | ValiditySnapshot | null | The committed validity snapshot, null until validation has run. |
ariaDescribedby | string | undefined | The composed aria-describedby, for wiring custom controls. |
validate | VoidFunction | Runs validation now, surfacing valueMissing even on a pristine field. |
clearErrors | VoidFunction | Clears committed errors and returns validity to the pristine (unvalidated) state. |
reset | VoidFunction | Resets interaction state and validity to pristine. |
getRootState | () => RootState | Returns the state of the root. |
getLabelState | () => LabelState | Returns the state of the label. |
getControlState | () => ControlState | Returns the state of the control (input, textarea, select, or a custom host). |
getHelperTextState | () => HelperTextState | Returns the state of the helper text. |
getErrorTextState | (props?: ErrorTextProps) => ErrorTextState | Returns the state of the error text. |
getIndicatorState | (props: IndicatorProps) => IndicatorState | Returns the state of the indicator. |
name | string | The name of the field. |
getValue | () => FieldValue | The current value of the field. |
getErrorText | () => string | null | The first error message of the field, null if there is none. |
getRootEl | () => HTMLElement | null | The root element of the field. |
getFormMachine | () => FormMachine | undefined | The machine of the form the field sits in, undefined outside of a form. |
setDisabled | (disabled: boolean) => void | Disables or enables the field. |
setRequired | (required: boolean) => void | Marks the field as required or optional. |
setReadOnly | (readOnly: boolean) => void | Makes the field read-only or editable. |
addDependencyChangeListener | (callback: (detail: FieldDependencyChangeDetail) => void) => () => void | Calls callback whenever the value of a field named in the listenTo prop of this field changes. Returns a function that removes the listener again. For a field without listenTo it never fires, but the returned function is still safe to call. |