> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/mui/base-ui/llms.txt
> Use this file to discover all available pages before exploring further.

# Forms

> A guide to building forms with Base UI components.

Base UI form control components extend the native [constraint validation API](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#the-constraint-validation-api) so you can build forms for collecting user input or providing control over an interface. They also integrate seamlessly with third-party libraries like [React Hook Form](#react-hook-form) and [TanStack Form](#tanstack-form).

## Naming form controls

Form controls must have an accessible name in order to be recognized by assistive technologies. `<Field.Label>` and `<Field.Description>` automatically assign the accessible name and description to their associated control:

```tsx title="Labeling select and slider" theme={null}
import { Form } from '@base-ui/react/form';
import { Field } from '@base-ui/react/field';
import { Select } from '@base-ui/react/select';
import { Slider } from '@base-ui/react/slider';

<Form>
  <Field.Root>
    <Field.Label>Time zone</Field.Label>
    <Field.Description>Used for notifications and reminders</Field.Description>
    <Select.Root />
  </Field.Root>

  <Field.Root>
    <Field.Label>Zoom level</Field.Label>
    <Field.Description>Adjust the size of the user interface</Field.Description>
    <Slider.Root />
  </Field.Root>
</Form>;
```

You can implicitly label `<Checkbox>`, `<Radio>` and `<Switch>` components by enclosing them with `<Field.Label>`:

```tsx title="Implicitly labeling a switch" theme={null}
import { Field } from '@base-ui/react/field';
import { Switch } from '@base-ui/react/switch';

<Field.Root>
  <Field.Label>
    <Switch.Root />
    Developer mode
  </Field.Label>
  <Field.Description>Enables extra tools for web developers</Field.Description>
</Field.Root>;
```

Compose `<Fieldset>` when a single label applies to multiple controls, such as a range slider with multiple thumbs or a section that combines several inputs. For checkbox and radio groups, keep the group label in `<Fieldset.Legend>` and wrap each option with `<Field.Item>`:

```tsx title="Composing range slider and radio group with fieldset" theme={null}
import { Form } from '@base-ui/react/form';
import { Field } from '@base-ui/react/field';
import { Fieldset } from '@base-ui/react/fieldset';
import { Radio } from '@base-ui/react/radio';
import { RadioGroup } from '@base-ui/react/radio-group';
import { Slider } from '@base-ui/react/slider';

<Form>
  <Field.Root>
    <Fieldset.Root render={<Slider.Root />}>
      <Fieldset.Legend>Price range</Fieldset.Legend>
      <Slider.Control>
        <Slider.Track>
          <Slider.Thumb />
          <Slider.Thumb />
        </Slider.Track>
      </Slider.Control>
    </Fieldset.Root>
  </Field.Root>

  <Field.Root>
    <Fieldset.Root render={<RadioGroup />}>
      <Fieldset.Legend>Storage type</Fieldset.Legend>
      <Radio.Root value="ssd" />
      <Radio.Root value="hdd" />
    </Fieldset.Root>
  </Field.Root>
</Form>;
```

## Building form fields

Pass the `name` prop to `<Field.Root>` to include the wrapped control's value when a parent form is submitted:

```tsx title="Assigning field name to combobox" theme={null}
import { Form } from '@base-ui/react/form';
import { Field } from '@base-ui/react/field';
import { Combobox } from '@base-ui/react/combobox';

<Form>
  <Field.Root name="country">
    <Field.Label>Country of residence</Field.Label>
    <Combobox.Root />
  </Field.Root>
</Form>;
```

## Submitting data

You can take over form submission using the native `onSubmit`, or custom `onFormSubmit` props:

```tsx title="Native submission using onSubmit" theme={null}
import { Form } from '@base-ui/react/form';

<Form
  onSubmit={async (event) => {
    // Prevent the browser's default full-page refresh
    event.preventDefault();
    // Create a FormData object
    const formData = new FormData(event.currentTarget);
    // Send the FormData instance in a fetch request
    await fetch('https://api.example.com', {
      method: 'POST',
      body: formData,
    });
  }}
/>;
```

When using `onFormSubmit`, you receive form values as a JavaScript object, with `eventDetails` provided as a second argument. Additionally, `preventDefault()` is automatically called on the native submit event:

```tsx title="Submission using onFormSubmit" theme={null}
import { Form } from '@base-ui/react/form';

<Form
  onFormSubmit={async (formValues) => {
    const payload = {
      product_id: formValues.id,
      order_quantity: formValues.quantity,
    };
    await fetch('https://api.example.com', {
      method: 'POST',
      body: JSON.stringify(payload),
    });
  }}
/>;
```

## Constraint validation

Base UI form components support native HTML validation attributes for many validation rules:

* `required` specifies a required field.
* `minLength` and `maxLength` specify a valid length for text fields.
* `pattern` specifies a regular expression that the field value must match.
* `step` specifies an increment that numeric field values must be an integral multiple of.

```tsx title="Defining constraint validation on a text field" theme={null}
import { Field } from '@base-ui/react/field';

<Field.Root name="website">
  <Field.Control type="url" required pattern="https?://.*" />
  <Field.Error />
</Field.Root>;
```

Base UI form components use a hidden input to participate in native form submission and validation. To anchor the hidden input near a control so the native validation bubble points to the correct area, ensure the component has been given a `name`, and wrap controls in a relatively positioned container for best results.

## Custom validation

You can add custom validation logic by passing a synchronous or asynchronous validation function to the `validate` prop, which runs after native validations have passed.

Use the `validationMode` prop to configure when validation is performed:

* `onSubmit` (default) validates all fields when the containing `<Form>` is submitted, afterwards invalid fields revalidate when their value changes.
* `onBlur` validates the field when focus moves away.
* `onChange` validates the field when the value changes, for example, after each keypress in a text field or when a checkbox is checked or unchecked.

`validationDebounceTime` can be used to debounce the function in use cases such as asynchronous requests or text fields that validate `onChange`.

```tsx title="Text input using custom asynchronous validation" theme={null}
import { Field } from '@base-ui/react/field';

<Field.Root
  name="username"
  validationMode="onChange"
  validationDebounceTime={300}
  validate={async (value) => {
    if (value === 'admin') {
      /* return an error message when invalid */
      return 'Reserved for system use.';
    }

    const result = await fetch(
      /* check the availability of a username from an external API */
    );

    if (!result) {
      return `${value} is unavailable.`;
    }

    /* return `null` when valid */
    return null;
  }}
>
  <Field.Control required minLength={3} />
  <Field.Error />
</Field.Root>;
```

## Server-side validation

You can pass errors returned by (post-submission) server-side validation to the `errors` prop, which will be merged into the client-side field state for display.

This should be an object with field names as keys, and an error string or array of strings as the value. Once a field's value changes, any corresponding error in `errors` will be cleared from the field state.

```tsx title="Displaying errors returned by server-side validation" theme={null}
import { Form } from '@base-ui/react/form';
import { Field } from '@base-ui/react/field';

async function submitToServer(/* payload */) {
  return {
    errors: {
      promoCode: 'This promo code has expired',
    },
  };
}

const [errors, setErrors] = React.useState();

<Form
  errors={errors}
  onSubmit={async (event) => {
    event.preventDefault();
    const response = await submitToServer(/* data */);
    setErrors(response.errors);
  }}
>
  <Field.Root name="promoCode" />
</Form>;
```

## Displaying errors

Use `<Field.Error>` without `children` to automatically display the field's native error message when invalid. The `match` prop can be used to customize the message based on the validity state, and manage internationalization from your application logic:

```tsx title="Customizing error message for a required field" theme={null}
<Field.Error match="valueMissing">You must create a username</Field.Error>
```

## React Hook Form

[React Hook Form](https://react-hook-form.com) is a popular library that you can integrate with Base UI to externally manage form and field state for your existing components.

### Initialize the form

Initialize the form with the `useForm` hook, assigning the initial value of each field by their name in the `defaultValues` parameter:

```tsx title="Initialize a form instance" theme={null}
import { useForm } from 'react-hook-form';

const { control, handleSubmit } = useForm<FormValues>({
  defaultValues: {
    username: '',
    email: '',
  },
});
```

### Integrate components

Use the `<Controller>` component to integrate with any `<Field>` component, forwarding the `name`, `field`, and `fieldState` render props to the appropriate part:

```tsx title="Integrating the controller component with Base UI field" theme={null}
import { useForm, Controller } from "react-hook-form"
import { Field } from '@base-ui/react/field';

const { control, handleSubmit} = useForm({
  defaultValues: {
    username: '',
  }
})

<Controller
  name="username"
  control={control}
  render={({
    field: { name, ref, value, onBlur, onChange },
    fieldState: { invalid, isTouched, isDirty, error },
  }) => (
    <Field.Root name={name} invalid={invalid} touched={isTouched} dirty={isDirty}>
      <Field.Label>Username</Field.Label>
      <Field.Description>
        May appear where you contribute or are mentioned. You can remove it at any time.
      </Field.Description>
      <Field.Control
        placeholder="e.g. alice132"
        value={value}
        onBlur={onBlur}
        onValueChange={onChange}
        ref={ref}
      />
      <Field.Error match={!!error}>
        {error?.message}
      </Field.Error>
    </Field.Root>
  )}
/>
```

### Field validation

Specify `rules` on the `<Controller>` in the same format as [`register`](https://react-hook-form.com/docs/useform/register) options, and use the `match` prop to delegate control of the error rendering:

```tsx title="Defining validation rules and displaying errors" theme={null}
import { Controller } from "react-hook-form"
import { Field } from '@base-ui/react/field';

<Controller
  name="username"
  control={control}
  rules={{
    required: 'This is a required field',
    minLength: { value: 2, message: 'Too short' },
    validate: (value) => {
      if (/* custom logic */) {
        return 'Invalid'
      }
      return null;
    },
  }}
  render={({
    field: { name, ref, value, onBlur, onChange },
    fieldState: { invalid, isTouched, isDirty, error },
  }) => (
    <Field.Root name={name} invalid={invalid} touched={isTouched} dirty={isDirty}>
      <Field.Label>Username</Field.Label>
      <Field.Control
        placeholder="e.g. alice132"
        value={value}
        onBlur={onBlur}
        onValueChange={onChange}
        ref={ref}
      />
      <Field.Error match={!!error}>
        {error?.message}
      </Field.Error>
    </Field.Root>
  )}
/>
```

### Submitting data

Wrap your submit handler function with `handleSubmit` to receive the form values as a JavaScript object for further handling:

```tsx title="Form submission handler" theme={null}
import { useForm } from 'react-hook-form';
import { Form } from '@base-ui/react/form';

interface FormValues {
  username: string;
  email: string;
}

const { handleSubmit } = useForm<FormValues>();

async function submitForm(data: FormValues) {
  // transform the object and/or submit it to a server
  await fetch(/* ... */);
}

<Form onSubmit={handleSubmit(submitForm)} />;
```

## TanStack Form

[TanStack Form](https://tanstack.com/form/v1/docs/overview) is a form library with a function-based API for orchestrating validations that can also be integrated with Base UI.

### Initialize the form

Create a form instance with the `useForm` hook, assigning the initial value of each field by their name in the `defaultValues` parameter:

```tsx title="Initialize a form instance" theme={null}
import { useForm } from '@tanstack/react-form';

interface FormValues {
  username: string;
  email: string;
}

const defaultValues: FormValues = {
  username: '',
  email: '',
};

const form = useForm<FormValues>({
  defaultValues,
});
```

### Integrate components

Use the `<form.Field>` component from the form instance to integrate with Base UI components using the `children` prop, forwarding the various `field` render props to the appropriate part:

```tsx title="Integrating TanStack Form with Base UI components" theme={null}
import { useForm } from '@tanstack/react-form';
import { Field } from '@base-ui/react/field';

const form = useForm(/* defaultValues, other parameters */)

<form>
  <form.Field
    name="username"
    children={(field) => (
      <Field.Root
        name={field.name}
        invalid={!field.state.meta.isValid}
        dirty={field.state.meta.isDirty}
        touched={field.state.meta.isTouched}
      >
        <Field.Label>Username</Field.Label>
        <Field.Control
          value={field.state.value}
          onValueChange={field.handleChange}
          onBlur={field.handleBlur}
          placeholder="e.g. bob276"
        />

        <Field.Error match={!field.state.meta.isValid}>
          {field.state.meta.errors.join(',')}
        </Field.Error>
      </Field.Root>
    )}
  />
</form>
```

### Submitting data

To submit the form:

1. Pass a submit handler function to the `onSubmit` parameter of `useForm`.
2. Call `form.handleSubmit()` from an event handler such as form `onSubmit` or `onClick` on a button.

```tsx title="Form submission handler" theme={null}
import { useForm } from '@tanstack/react-form';

const form = useForm({
  onSubmit: async ({ value: formValues }) => {
    await fetch(/* POST the formValues to an external API */);
  },
});

<form
  onSubmit={(event) => {
    event.preventDefault();
    form.handleSubmit();
  }}
>
  {/* form fields */}
  <button type="submit">Submit</button>
</form>;
```
