Patterns
Forms and validation
The validation flowLink to the section The validation flow
Before typing
Say the rules in the description, such as “Up to 40 characters”, so nobody learns them from an error.
While typing
Stay quiet. An error for a field the reader is still filling in is noise.
On leaving a changed field
Check that field and show its error inline. Leave untouched fields alone.
On submit
Check everything. Show each error under its field and a FormErrorSummary at the top, which takes focus and links to each field.
While fixing
Update each inline error as the reader types. The summary stays as it was until the next submit, so focus never jumps.
After success
Say what happened, next to the button with FormActions, in a toast, or as a flash message on the next page.
<form noValidate onSubmit={submit}>
{summary.length > 0 && <FormErrorSummary title={t('form.problems', { count: summary.length })} errors={summary} />}
<TextField id="email" label={t('email')} type="email" autoComplete="email" isRequired
onBlur={check('email')} isInvalid={!!errors.email} errorMessage={errors.email} />
<FormActions status={status}>
<Button type="submit" isPending={sending} pendingLabel={t('sending')}>{t('send')}</Button>
</FormActions>
</form>Labels, hints and marksLink to the section Labels, hints and marks
- Every field has a visible label above it. A placeholder is an example, never the label: it disappears while typing.
- Mark the smaller group. When most fields are required, mark the optional ones with
optionalLabel; otherwise mark the required ones.requiredLabelreplaces the asterisk with a word. - Put rules in
description. It is linked to the field, so a screen reader reads it after the label. - Use
statusfor good news and hints that arrive while typing, such as “Name is free”, in the ok, info or warn tone.
Types and autocompleteLink to the section Types and autocomplete
The right type brings the right keyboard on phones, and autocomplete lets browsers and password managers fill the field, which WCAG asks for on personal data.
| Field | type | autoComplete |
|---|---|---|
| Full name | text | name |
email | email | |
| Phone | tel | tel |
| Current password | password | current-password |
| New password | password | new-password |
| One-time code | text, inputMode="numeric" | one-time-code |
| Organisation | text | organization |
| Postal code | text | postal-code |
Layout from 320pxLink to the section Layout from 320px
- One column, about 32rem wide at most. The eye moves down, not in a zigzag.
- Two fields share a row only when they belong together and are short, such as a postal code and a city, and only from the
smbreakpoint up. - Group related fields in a
fieldsetwith alegend, or use CheckboxGroup and RadioGroup, which do it for you. - FormActions puts the main button last on wide screens and stacks the buttons at full width on narrow ones, main button on top.
- A long form keeps its buttons in view with
sticky.
Density in formsLink to the section Density in forms
Forms on the web keep the comfortable density: 36px controls and room between fields. The compact density is for the desktop tools, where many settings sit on one screen. Both keep targets above 24px, and field text stays at 16px on phones so iOS does not zoom.