Form
Forms contain fields of controls to enter information for submission.
'use client';
import * as React from 'react';
import { Form } from '@base_ui/react/Form';
import { Fieldset } from '@base_ui/react/Fieldset';
import { Field } from '@base_ui/react/Field';
import { styled } from '@mui/system';
type Status = 'initial' | 'loading' | 'success' | 'error';
export default function FormIntroduction() {
const [errors, setErrors] = React.useState({});
const [status, setStatus] = React.useState<Status>('initial');
return (
<FormRoot
errors={errors}
onClearErrors={setErrors}
onSubmit={async (event) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const username = formData.get('username') as string;
const password = formData.get('password') as string;
setStatus('loading');
// Mimic a server request
await new Promise((resolve) => {
setTimeout(resolve, 500);
});
const isUnknownUser = username !== 'admin';
const isInvalidPassword = password !== 'admin';
const serverErrors: Partial<Record<'username' | 'password', string>> = {};
if (isUnknownUser) {
serverErrors.username = 'Username does not exist.';
setStatus('error');
} else if (isInvalidPassword) {
serverErrors.password = 'Invalid password.';
setStatus('error');
} else {
setStatus('success');
}
setErrors(serverErrors);
}}
>
<FieldsetRoot>
<FieldsetLegend>App login</FieldsetLegend>
<p>
Username and password are both <code>admin</code> to log in.
</p>
<Field.Root name="username">
<Field.Label>Username</Field.Label>
<FieldControl required />
<FieldError />
</Field.Root>
<Field.Root name="password">
<Field.Label>Password</Field.Label>
<FieldControl type="password" required />
<FieldError />
</Field.Root>
</FieldsetRoot>
<FormSubmit disabled={status === 'loading'}>
{status === 'loading' ? 'Logging in...' : 'Log in'}
</FormSubmit>
{status === 'success' && (
<FormSuccess role="alert" aria-live="polite">
Successfully logged in
</FormSuccess>
)}
</FormRoot>
);
}
const FormRoot = styled(Form.Root)`
width: 275px;
`;
const FieldControl = styled(Field.Control)`
border: 1px solid #ccc;
border-radius: 4px;
width: 100%;
padding: 6px;
font-size: 100%;
&[data-field='invalid'] {
border-color: red;
background-color: rgb(255 0 0 / 0.1);
}
&:focus {
outline: 0;
border-color: #0078d4;
box-shadow: 0 0 0 3px rgba(0 100 255 / 0.3);
&[data-field='invalid'] {
border-color: red;
box-shadow: 0 0 0 3px rgba(255 0 0 / 0.3);
}
}
`;
const FieldError = styled(Field.Error)`
font-size: 90%;
margin: 0;
margin-bottom: 0;
margin-top: 4px;
line-height: 1.1;
color: red;
`;
const FormSuccess = styled('p')`
font-size: 90%;
margin: 0;
padding: 0;
margin-top: 4px;
color: green;
`;
const FieldsetRoot = styled(Fieldset.Root)`
border: none;
padding: 0;
display: flex;
flex-direction: column;
gap: 10px;
p {
margin: 0;
color: grey;
font-size: 90%;
}
`;
const FieldsetLegend = styled(Fieldset.Legend)`
display: block;
font-size: 110%;
font-weight: 600;
`;
const FormSubmit = styled('button')`
display: block;
margin-top: 10px;
padding: 10px;
width: 100%;
font-size: 100%;
background-color: #0078d4;
color: white;
border: none;
border-radius: 4px;
&[aria-disabled='true'] {
background-color: #ddd;
color: black;
}
`;
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 { Form } from '@base_ui/react/Form'; import { Field } from '@base_ui/react/Field';
Anatomy
Forms are implemented using a Root
component and Field
components:
<Form.Root />
renders the<form>
element.<Field.Root />
renders an individual Field element.
Usage
Forms are intended to be used with the Field
component, which provides labeling and validation for individual form controls. These are nested inside Form.Root
:
If any of the Fields within the Form are invalid upon submit, focus is moved to the first invalid Field's control and the submit event is prevented.
Validation
The Field.Error
subcomponent of a Field renders error messages inside of it, with its content automatically populated with any client-side validation messages that occur.
Server-side validation
For server-side validation messages, the Form.Root
component accepts an errors
prop — an object whose keys map to the Field name
prop, with each value being a string or array of strings representing error messages. The onClearErrors
prop is called to clear these external server errors when the field's control has been changed:
For more flexibility if required, each Field.Root
component accepts an invalid
boolean prop, and each Field.Error
subcomponent accepts a forceShow
boolean prop. These can be used as an alternative to Form.Root
's errors
prop by manually targeting specific Fields and showing specific error messages.
Native validation
By default, browser-native validation popups are disabled, as Field.Error
replaces this by rendering the validation messages to allow for flexible styling. If necessary, to enable these native validation popups, re-apply the default prop:
API Reference
FormRoot
Prop | Type | Default | Description |
---|---|---|---|
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
errors | object | Object of error messages with each key mapping to the name prop of a Field control, usually from server-side validation. | |
onClearErrors | func | Callback fired when the external server-side error messages should be cleared. | |
render | union | A function to customize rendering of the component. |