Field
Fields represent an individual section of a form containing an associated control and label, as well as any description or validation messages.
Your name will be visible on your profile.
Installation
Base UI components are all available as a single package.
npm install @base_ui/react
Once you have the package installed, import the component.
import * as Field from '@base_ui/react/Field';
Anatomy
Fields are implemented using a collection of related components:
<Field.Root />
is a top-level component that wraps all other components.<Field.Control />
renders the control when not using a native Base UI input component.<Field.Label />
renders a label for the control.<Field.Description />
renders an optional description for the control to provide additional information.<Field.Error />
renders error messages for the control.<Field.Validity />
accepts a function as a child that enables reading rawValidityState
to render custom JSX.
<Field.Root>
<Field.Control />
<Field.Label />
<Field.Description />
<Field.Error />
<Field.Validity />
</Field.Root>
Labeling and descriptive help text
All Base UI input components are aware of Base UI's Field
component. The label and description are automatically wired to these components when placed inside a Field.Root
:
<Field.Root>
<Checkbox.Root>
<Checkbox.Indicator />
</Checkbox.Root>
<Field.Label>My checkbox</Field.Label>
<Field.Description>My description</Field.Description>
</Field.Root>
When using a native control like input
or a custom component which is not aware of Base UI's Field
, use Field.Control
:
<Field.Root>
<Field.Control />
<Field.Label>My input</Field.Label>
<Field.Description>My description</Field.Description>
</Field.Root>
The render
prop allows you to pass a custom component or tag, different from the default of input
:
<Field.Control render={<select />} />
Validation
When adding native HTML validation props like required
or pattern
, Field.Error
renders error messages inside of it automatically:
<Field.Root>
<Field.Label>My input</Field.Label>
<Field.Control required />
<Field.Error />
</Field.Root>
The children
by default is the browser's native message, which is automatically internationalized. You may pass custom children
instead:
<Field.Root>
<Field.Control required />
<Field.Error>Field is required</Field.Error>
</Field.Root>
Individual constraint validation failures
When there are multiple HTML validation props, you can target individual validity state failures using the show
prop to render custom messages:
<Field.Root>
<Field.Control required pattern="[a-zA-Z0-9]+" />
<Field.Error show="valueMissing">Field is required</Field.Error>
<Field.Error show="patternMismatch">
Only alphanumeric characters allowed
</Field.Error>
</Field.Root>
For the list of supported show
strings, visit ValidityState
on MDN.
Custom validation
In addition to the native HTML constraint validation, custom validation can be used by specifying a validate
function on Field.Root
. It receives the control's value
as its argument, and returns an error string or array of error strings if the field is invalid, or null
otherwise.
<Field.Root
validate={(value) =>
value === 'password' ? 'Cannot literally use `password` as your password.' : null
}
>
<Field.Control type="password" />
<Field.Label>Password</Field.Label>
<Field.Error />
</Field.Root>
To customize the rendering of multiple messages, you can use the Validity
subcomponent:
<Field.Root
validate={(value) => {
const errors = [];
if (value.length < 8) {
errors.push('Password must be at least 8 characters long.');
}
if (value === 'password') {
errors.push('Cannot literally use `password` as your password.');
}
return errors;
}}
>
<Field.Control type="password" />
<Field.Label>Password</Field.Label>
<Field.Error>
<ul>
<Field.Validity>
{(state) => state.errors.map((error) => <li key={error}>{error}</li>)}
</Field.Validity>
</ul>
</Field.Error>
</Field.Root>
The Validity
subcomponent enables rendering custom JSX based on the state
parameter, which contains the following properties:
state.validity
, the field'sValidityState
state.errors
, an array of custom errors returned from thevalidate
prop (if present)state.error
, a custom error string returned from thevalidate
prop (if present)state.value
, the field control's current valuestate.initialValue
, the field control's initial value upon mount
It can be placed anywhere inside Field.Root
, including other Field subcomponents.
Controlled validity
When the invalid
prop is applied to Field.Root
, the Field is placed into an invalid state regardless of client-side validation. In this state, a given Field.Error
message can be forced to be shown by specifying a forceShow
prop.
This is useful for server-side error messages, or displaying errors initially during SSR phase.
const [serverErrors, setServerErrors] = React.useState({
email: false,
});
return (
<Field.Root invalid={serverErrors.email}>
<Field.Control type="email" required />
<Field.Error show="valueMissing">Client-side only error message</Field.Error>
<Field.Error show="typeMismatch" forceShow={serverErrors.email}>
Client + server-side error message
</Field.Error>
<Field.Error forceShow={serverErrors.email}>
Server-side only message
</Field.Error>
</Field.Root>
);
The show
prop is for client-side validation, while the forceShow
prop is for server-side validation. Both can be combined together to share the same error message.
Performing an email validity check on the server:
Errors shown initially for password validation:
- Password must be at least 8 characters long.
- Password must contain at least 2 uppercase letters.
- Password must contain at least 2 unique symbols from the set [!@#$%^&*].
Realtime and async validation
validateOnChange
reports the validity of the control on every change
event, such as a keypress:
<Field.Root validateOnChange>
The validate
function can also be async by returning a promise, enabling inline server-side validation through network requests.
In the demo below, the taken names are admin
, root
, and superuser
— every other name is available. For demonstration purposes, a fake network request that takes 500ms is initiated to mimic a trip to the server to check for availability on the back-end.
Handle availability checker
Enter a name
The change
validation is debounced by 500ms to avoid firing a network request on every keystroke by specifying the validateDebounceTime
prop:
<Field.Root validateOnChange validateDebounceTime={500}>
Styling
The [data-field="valid"]
and [data-field="invalid"]
style hooks determine if the field is invalid or not:
<Field.Root>
<Field.Control required className="FieldControl" />
</Field.Root>
.FieldControl[data-field='invalid'] {
color: red;
}
[data-touched]
is applied if the field has been "touched": blurred after being interacted with, or submitted if pressing Enter on an input.
.FieldControl[data-touched] {
color: red;
}
[data-dirty]
is applied if the field's value has been changed from its initial one.
.FieldControl[data-dirty] {
color: orange;
}