const FormExample = () => {const seed = useUIDSeed();return (<Form aria-labelledby={seed('address-heading')}><Headingas="h3"variant="heading30"id={seed('address-heading')}marginBottom='space0'>Create new address</Heading><FormControl><Label htmlFor={seed('address-type')}>Address type</Label><Select id={seed('address-type')} name="address-type"><Option value="1">Option 1</Option><Option value="2">Option 2</Option></Select></FormControl><FormControl><Label htmlFor={seed('friendly-name')}>Friendly name</Label><Inputtype="text"id={seed('friendly-name')}name="friendly-name"placeholder="Add friendly name"/></FormControl><FormControl><RadioGrouplegend="Address implementation"name="address-implementation"><Radiovalue="conversations"helpText="Supports both inbound and outbound SMS and attachments.">Conversations</Radio><Radiovalue="legacy"helpText="Supports inbound SMS.">Legacy</Radio></RadioGroup></FormControl><FormActions><Button variant="primary">Submit</Button><Button variant="secondary">Cancel</Button></FormActions></Form>);}render(<FormExample />)
Use the Form component to arrange a layout of form elements with preset spacing. The Form component renders an HTML form element and comes with default behavior from the browser (learn more about HTML form elements). Form elements are displayed vertically by default, but also support horizontal and 2-column layouts.
Common form components include Buttons, Comboboxes, Checkboxes, Date Picker, File Picker, Form Pill Groups, Help Text, Inputs, Label, Radio Group, Selects, Switch, Textarea, and Time Picker.
Designing a good form requires making decisions about composition, sequence, form elements, copy, and feedback.
- Forms often have a header, with a Heading and Paragraph that explains the purpose of the form or form section. The header can also include additional contextual components like Alerts or Callouts for expressing errors or other important information.
- Forms can include form actions, often with a primary Button ordered first, then a secondary Button second.
- More complex forms should be split into sections, known as fieldsets. Form sections are separated with a Separator. Sections often have their own header, with a Heading, known as a legend, and can also include a Paragraph to explain the section.
- Order forms sequentially, following the natural flow of the user's language preference. Typically top to bottom, and left to right for Latin-based languages.
- Only use a single Form per page.
- Don’t use CSS to control element order. Form elements should be displayed in the same order in the HTML as they appear on screen, as screen readers announce form elements in the order they appear in the HTML.
- Provide accessible and clear Labels and Help Text.
- Labels should clearly describe the value being requested.
- Labels should be short and succinct, generally 1-3 words.
- Help Text is optional. Use it to describe what makes the form field successful and help users avoid errors.
- Avoid placeholder text. It is not broadly supported by assistive technologies, does not display in older browsers, and disappears from the field when a user enters text.
- Use fieldsets and legends.
- When you need to group related form elements, use a form section, which renders a HTML fieldset. To add clarity to the section, use a form section Heading, which renders a HTML legend, to clearly describe the group. It should be concise and descriptive.
- You can have nested form sections if applicable.
- Embed multiple fieldsets and legends for more complex forms.
- Use Help Text for form validation errors. For successful submissions, use a Toast
- Don’t make the user repeat information. For example, asking for users to input a shipping address and billing address if the address is the same information.
- Mark all required form elements as required.
- Consider breaking long forms into smaller sections or pages, as they can place more cognitive load on the user.
- Follow best practices for accessibility for each component used within the Form.
- Use a single column layout, as it can be more difficult for users with limited vision to scan from right to left if a multi-column layout is used.
There are no special keyboard interactions for the Form component. The user should be able to tab through the form elements in the natural tab order.
This example combines all of the separate features of the Form component into one. It shows how all the features work together harmoniously through a composition.
Accessibility insight
Make sure to connect the Label to the Input. This is done with the htmlFor
prop on the label, and the id
prop on the input. Those two need to match.
If the input has any associated help text, the input should also use the aria-describedby
prop that equals the id
of the help text. This ensures screen readers know the help text ties directly to the input.
Single-column layouts are easier to read. To learn more, check out some research on form readability. Horizontal layouts can create problems for people who rely on the structural layout of the page. Read more on single column vs. multi-column layouts.
const FormExample = () => {const seed = useUIDSeed();return (<Form aria-labelledby={seed('address-heading')}><Headingas="h3"variant="heading30"id={seed('address-heading')}marginBottom='space0'>Create new address</Heading><FormControl><Label htmlFor={seed('address-type')}>Address type</Label><Select id={seed('address-type')} name="address-type"><Option value="1">Option 1</Option><Option value="2">Option 2</Option></Select></FormControl><FormControl><Label htmlFor={seed('friendly-name')}>Friendly name</Label><Inputtype="text"id={seed('friendly-name')}name="friendly-name"placeholder="Add friendly name"/></FormControl><FormControl><RadioGrouplegend="Address implementation"name="address-implementation"><Radiovalue="conversations"helpText="Supports both inbound and outbound SMS and attachments.">Conversations</Radio><Radiovalue="legacy"helpText="Supports inbound SMS.">Legacy</Radio></RadioGroup></FormControl><FormActions><Button variant="primary">Submit</Button><Button variant="secondary">Cancel</Button></FormActions></Form>);}render(<FormExample />)
Use more than 1 column only when it is clearer than a single column and prevents users from inputting incorrect information. Reserve 2-column layouts for when inputs are organized linearly or as a coherent entity, like dates, first and last name, or, ZIP, city, and state or credit card details fields. Read more on single column vs. multi-column layouts.
const FormExample = () => {const seed = useUIDSeed();return (<Form aria-labelledby={seed('date-time-heading')}><Heading as="h3" variant="heading30" id={seed('date-time-heading')} marginBottom='space0'>Custom date/time range</Heading><Paragraph marginBottom="space0">All dates/times in UTC. Usage data available up to 7 days.</Paragraph><FormControlTwoColumn><FormControl><Label htmlFor={seed('start-date')}>Start date</Label><DatePicker id={seed('start-date')}/></FormControl><FormControl><Label htmlFor={seed('end-date')}>End date</Label><DatePicker id={seed('end-date')}/></FormControl></FormControlTwoColumn><FormControlTwoColumn><FormControl><Label htmlFor={seed('start-time')}>Start time</Label><TimePicker id={seed('start-time')}/></FormControl><FormControl><Label htmlFor={seed('end-time')}>End time</Label><TimePicker id={seed('end-time')}/></FormControl></FormControlTwoColumn><FormActions><Button variant="primary">Apply</Button></FormActions></Form>);}render(<FormExample />)
Use a horizontal layout when options can be placed next to each other in a logical order and the labels for each element are shorter than 3 words.
const FormExample = () => {const seed = useUIDSeed();return (<Form aria-labelledby={seed('access-profile-heading')}><Headingas="h3"variant="heading30"id={seed('access-profile-heading')}marginBottom='space0'>Choose Network Access Profile</Heading><Paragraph marginBottom="space0">Network Access Profile specifies what networks your SIM's would connect to and how you will be billed.</Paragraph><FormControl><CheckboxGrouplegend="Select number type"name="number-type"orientation="horizontal"><Checkbox checked value="local">Local</Checkbox><Checkbox value="mobile">Mobile</Checkbox><Checkbox value="toll-free">Toll-free</Checkbox></CheckboxGroup></FormControl><FormControl><RadioGrouplegend="Select payment method"name="payment-method"orientation="horizontal"><Radio value="cc">Credit card</Radio><Radio value="pp">PayPal</Radio><Radio value="dd">Direct deposit</Radio></RadioGroup></FormControl><FormActions><Button variant="primary">Save</Button><Button variant="secondary">Cancel</Button></FormActions></Form>);}render(<FormExample />)
Use sections to group related content or steps within a Form. A section is separated using a Separator, with $space-90
above and below it.
Section headings and descriptions are optional.
If a form element doesn’t belong to a form sections, make sure there's at least $space-130
between the form element and surrounding sections.
const FormExample = () => {const seed = useUIDSeed();return (<Form aria-labelledby={seed('alarm-heading')}><Headingas="h3"variant="heading30"id={seed('alarm-heading')}marginBottom='space0'>Create alarm</Heading><Paragraph marginBottom="space0">Use alarms to get alerted to important events happening with your account.</Paragraph><FormSection><FormSectionHeading>Step 1</FormSectionHeading><FormSectionDescription>Name your alarm and select the error code you’d like to set an alarm. Use the historical trend graph for reference.</FormSectionDescription><FormControl><Label htmlFor={seed('alarm-name')}>Alarm name</Label><Inputtype="text"id={seed('alarm-name')}aria-describedby={seed('alarm-help-text')}/><HelpText id={seed('alarm-help-text')}>Consider a recognizable name related to its use case.</HelpText></FormControl><FormControl><Label htmlFor={seed('error-code')}>Select error code</Label><Inputid={seed('error-code')}type="text"value="11200 - HTTP retrieval failure"insertAfter={<SearchIcon color="colorTextIcon" decorative/>}/></FormControl></FormSection><Separator orientation="horizontal"/><FormSection><FormSectionHeading>Step 2</FormSectionHeading><FormSectionDescription>Determine when the alarm should be activated.</FormSectionDescription><FormControl><Label htmlFor={seed('num-errors')}>Number of errors</Label><Inputtype="text"id={seed('num-errors')}value="20"/></FormControl><FormControl><Label htmlFor={seed('time-period')}>Time period</Label><Select id={seed('time-period')}><Option value="minute">Every minute</Option><Option value="hour">Every hour</Option><Option value="day">Every day</Option></Select></FormControl></FormSection><FormActions><Button variant="primary">Save</Button><Button variant="secondary">Cancel</Button></FormActions></Form>);}render(<FormExample />)
A Form can include form actions if placed within a page or page section. Using form actions helps keep the form elements left-aligned for better scannability of the content and its related actions. Don't prevent form submission by disabling the submit button. Use error messages to explain what information is missing or incorrect.
const FormActionsExample = () => {return (<FormActions><Button variant="primary">Submit</Button><Button variant="secondary">Cancel</Button></FormActions>);}render(<FormActionsExample />)
Use validation and error messaging to indicate when a form submission fails or requires additional information to be shown.
Validate form fields on form submission. Validating a form field when the user leaves the current field (on blur) can be helpful to check for syntax errors. However, this can be frustrating to users who tab through controls to navigate a page, and to screen reader users, who might not be able to detect that an error occurred on blur.
Don't prevent form submission by disabling the submit button. Assistive technologies cannot detect disabled buttons. Use error messages to explain what information is missing or incorrect.
Use Help Text to show inline error messaging that informs users that they cannot continue. Provide guidance on next steps and how to remedy the situation.
If there are multiple errors, use Help Text to show inline error messages and a Callout to show an error summary. Place the Callout below the form title or section title, if applicable, and above the form elements.
If the Form lives within a Modal or Side Modal and has a form action to submit or save the information, we suggest closing the Modal on submit, then display a Toast that indicates the action was successful or experienced a system error.
Error messaging for required fields should explain how to resolve the error and not reiterate that the field is required. The required field indicator is sufficient. For additional guidance on how to compose error messages, refer to the error state pattern.
Ideally, Help Text should have enough information to help users prevent errors. If Help Text is already on a form field, change it to variant=“error”
and add error copy before the original help text copy.
const statesList = [{value: 'AK', name: 'Alaska'},{value: 'AL', name: 'Alabama'},{value: 'AZ', name: 'Arizona'},{value: 'CA', name: 'California'},{value: 'CO', name: 'Colorado'},{value: 'CT', name: 'Connecticut'},{value: 'ID', name: 'Idaho'},{value: 'IL', name: 'Illinois'},{value: 'IN', name: 'Indiana'},{value: 'KS', name: 'Kansas'},{value: 'KY', name: 'Kentucky'},{value: 'LA', name: 'Louisiana'},{value: 'MN', name: 'Minnesota'},{value: 'MO', name: 'Missouri'},{value: 'MS', name: 'Mississippi'},{value: 'MT', name: 'Montana'},];const FormExample = () => {const seed = useUIDSeed();return (<Form aria-labelledby={seed('settings-heading')}><Headingid={seed('settings-heading')}variant="heading30"marginBottom="space0">Settings</Heading><Callout variant="error"><CalloutHeading as="h4">These fields are missing values:</CalloutHeading><CalloutList as="ul"><CalloutListItem>Street address</CalloutListItem><CalloutListItem>Zip code</CalloutListItem><CalloutListItem>Email address</CalloutListItem></CalloutList></Callout><FormControl><Label htmlFor={seed('street-address')} required>Street address</Label><Inputid={seed('street-address')}type="text"hasErrorrequiredaria-describedby={seed('street-address-error')}/><HelpText variant="error" id={seed('street-address-error')}>Enter a street address.</HelpText></FormControl><FormControlTwoColumn><FormControl><Label htmlFor={seed('state')}>State</Label><Select id={seed('state')}>{statesList.map(({value, name}) => (<Option value={value} key={value}>{name}</Option>))}</Select></FormControl><FormControl><Label htmlFor={seed('zip')} required>Zip code</Label><Input id={seed('zip')} aria-describedby={seed('zip-error')} type="text" hasError required /><HelpText variant="error" id={seed('zip-error')}>Enter a zip code.</HelpText></FormControl></FormControlTwoColumn><FormControl><Label htmlFor={seed('email')} required>Email address</Label><Inputid={seed('email')}aria-describedby={seed('email-error')}type="email"hasErrorrequiredvalue="email@xyz.yxz"/><HelpText variant="error" id={seed('email-error')}>Enter an email address.</HelpText></FormControl><FormActions><Button variant="primary">Save</Button><Button variant="secondary">Cancel</Button></FormActions></Form>);}render(<FormExample />)
Forms can be set to any width needed for the desired form composition. When no max width is set, the form will be set to 100% of the parent container.
const FormExample = () => {const seed = useUIDSeed();return (<Form aria-labelledby={seed('address-heading')}><Headingas="h3"variant="heading30"id={seed('address-heading')}marginBottom='space0'>Create new address</Heading><FormControl><Label htmlFor={seed('address-type')}>Address type</Label><Select id={seed('address-type')} name="address-type"><Option value="1">Option 1</Option><Option value="2">Option 2</Option></Select></FormControl><FormControl><Label htmlFor={seed('friendly-name')}>Friendly name</Label><Inputtype="text"id={seed('friendly-name')}name="friendly-name"placeholder="Add friendly name"/></FormControl><FormControl><RadioGrouplegend="Address implementation"name="address-implementation"><Radiovalue="conversations"helpText="Supports both inbound and outbound SMS and attachments.">Conversations</Radio><Radiovalue="legacy"helpText="Supports inbound SMS.">Legacy</Radio></RadioGroup></FormControl><FormActions><Button variant="primary">Submit</Button><Button variant="secondary">Cancel</Button></FormActions></Form>);}render(<FormExample />)
For fixed-width needs, you can use field length to help hint at the length of content required.
const FormExample = () => {const seed = useUIDSeed();return (<Form aria-labelledby={seed('address-heading')} maxWidth="size30"><Headingas="h3"variant="heading30"id={seed('address-heading')}marginBottom='space0'>Create new address</Heading><FormControl><Label htmlFor={seed('address-type')}>Address type</Label><Select id={seed('address-type')} name="address-type"><Option value="1">Option 1</Option><Option value="2">Option 2</Option></Select></FormControl><FormControl><Label htmlFor={seed('friendly-name')}>Friendly name</Label><Inputtype="text"id={seed('friendly-name')}name="friendly-name"placeholder="Add friendly name"/></FormControl><FormControl><RadioGrouplegend="Address implementation"name="address-implementation"><Radiovalue="conversations"helpText="Supports both inbound and outbound SMS and attachments.">Conversations</Radio><Radiovalue="legacy"helpText="Supports inbound SMS.">Legacy</Radio></RadioGroup></FormControl><FormActions><Button variant="primary">Submit</Button><Button variant="secondary">Cancel</Button></FormActions></Form>);}render(<FormExample />)
The Form component ensures there is adequate spacing between form elements so users know which label belongs to which input. Typically, 50-75% of the height of a form element helps create this adequate spacing, so we’ve chosen to use $space-80
between all form elements.
For most forms, use a single-column layout. Multiple columns disrupt a user's vertical momentum, and can cause users to interpret the fields inconsistently. For a form with a horizontal layout, the user would need to scan in Z-patterns, which can inhibit comprehension and completion of the form. But if a form is in a single column, the path to completion is clearer, as it’s a straight line down the page.
Group related information in logical sections to aid in scanning and completion of a form. The flow from one set of questions to the next will better resemble a conversation. Grouping related fields together also helps users make sense of the information that they must fill in.
The process of completing forms should be as simple and easy as possible. Take the time to evaluate every question you add to your forms and strive for succinctness. Be vigilant about removing everything that isn’t necessary.
Before adding more form elements, ask yourself:
- Do you really need to ask this question?
- Is it information that you can get automatically?
- Is there a better time or place to get an answer from our users?
When ordering your form, use logical sequencing. Questions should be asked logically from a user’s perspective, not the application or database’s logic. For example, it’s unusual to ask for someone’s address before their name.
Once you’ve determined how many form fields to include in a form, you’ll need to decide how to best break the form into sections. If a form naturally breaks down into a few short topics, a single page is likely to be a good way to organize the form. When a form becomes long and has a large number of questions that are only related by a few topics, multiple pages may be a better way to organize the form.
Where possible, ensure that field lengths provide meaningful affordances that help people answer questions effectively. Otherwise, use a consistent length that provides enough room for correct answers.
Make required and optional fields distinguishable. Try to avoid optional input fields in forms.
- If most of the inputs on a form are required, indicate the few that are optional.
- If most of the inputs on a form are optional, indicate the few that are required.
Text is the clearest way to indicate whether a field is required or optional. However, the required symbol is relatively well understood. If you use the required symbol to indicate required fields, you'll see a "Required" title on the symbol. If you're building for other languages, use the i18nLabel
prop to translate the "Required" title.
Component | When to use |
---|---|
Radio Group or Radio Button Group | Use it when you have a list of up to 6 fixed items, and users need to select only one at a time. |
Select or Singleselect Combobox | Use it when you have more than 6 options in a fixed list and users need to select only one at a time. |
Singleselect Combobox - Autocomplete | Use it when you have lists with over 15 options or if users need to search through a database and select a single option at a time. |
Component | When to use |
---|---|
Checkbox Group | Use it when you have a list of up to 6 fixed items, and users can select multiple values at a time. |
Multiselect Combobox | Use it when you have lists with over 6 options or if users need to search through a database and select multiple values at a time. |
Component | When to use |
---|---|
Slider | Use it when the exact value doesn’t matter. |
Input with number functionality | Use it when the exact numeric value matters. |
Date Picker | Use it for selecting specific dates or date ranges. |
Time Picker | Use it for selecting specific times or time ranges. |
Use a Form when users are expected to enter more than a single form component to collect user input.
Do
Use single column layouts as much as possible.
Don't
Don’t rely on 2-column or multi-column form layouts, as they disrupt a user's vertical momentum, and can cause users to interpret the fields inconsistently.
Do
Only include the necessary number of form fields. Strive for succinctness in each question asked.
Don't
Don’t add gratuitous optional form fields.
Do
Group related form elements. Consider breaking forms into sections when it makes sense for the content.
Don't
Don’t separate related form elements. Don’t create long forms that are unorganized.
Do
Write legend text to describe a group and their intended relationship together.
Don't
Don't leave sections without a legend. Don't use the legend and label text in a way that is intended to be read as a sentence.
Do
Use fieldsets when breaking a form into sections.
Don't
Don’t use typography components such as Heading when breaking forms into sections.
Do
Enable the form submission action, even when the form is empty or has an error.
Don't
Don't disable the submit button.
Do
Include a visible label on every form element.
Don't
Don't use placeholder or prefix/suffix text as a replacement for a label.
Do
Only use a single form per page.
Don't
Don’t use multiple forms per page.
Do
Implement the best field length needed for the design.
Don't
Don’t implement a full-width form for every design, especially within a page. Long line lengths, with characters greater than 80 characters per line, are difficult to read.
Do
Error text should explain how to resolve the error. For example, 'Add a valid zip code.'
Don't
Don’t blame the user for the error, such as: 'You did not add a valid zip code.' Don’t focus on whether a field is required within error text, such as: 'Adding a valid zip code is required.'
Further reading on Forms best practices.
- Kathryn Whitenton, Website Form Usability: Top 10 Recommendations (Nielsen Norman Group)
- Katie Sherwin, Placeholders in Form Fields are Harmful (Nielsen Norman Group, 2014)
- Andrew Coyle, Design Better Forms (UX Collective, 2016)
- Adam Silver, Form Design: From zero to hero all in one blog post (2019)
- Luke Wroblewski, Web Form Design: Filling in the blanks (2008)