Skip to content
+

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 raw ValidityState 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's ValidityState
  • state.errors, an array of custom errors returned from the validate prop (if present)
  • state.error, a custom error string returned from the validate prop (if present)
  • state.value, the field control's current value
  • state.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:

On the client, standard email validation is performed. On the server, we check a blocklist of email domains: the blocked domain is @example.com.

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;
}