Atoms
Form Controls
The parts people fill in or set, such as text inputs, selects, checkboxes, switches and sliders.
Code
<div class="field">
<label for="default-name" class="field__label label">Your name</label>
<input id="default-name" type="text" class="input" autocomplete="name" />
</div>
Why it exists
A form is where a page asks the most of someone. The browser already knows a lot about that job: the right on-screen keyboard, autofill, keyboard use, how to tell a screen reader what a control is. Every control here is the native element with a class, so all of that keeps working.
Text inputs, Selects and Textareas share one base, so they line up and change together, and every prominence keeps an edge of at least 3:1, because a field people can’t find can’t be filled in. The browser still draws the Checkbox and radio, so they stay legible in forced colours.
Styling is opt-in through classes. A bare <input> keeps its browser rendering, so anything you embed from elsewhere isn’t restyled by accident.
When to use
- Input, textarea: text people type, one line or several.
- Select: one option from a list they don’t need to search. To search it, use a Combobox.
- Checkbox, radio: several choices, or one of a few, all in view.
- Switch: a setting that takes effect at once.
- Range: a value where the rough amount matters more than the exact number.
Rules
- Classes only. Bare
<input>,<select>and<textarea>keep browser styling. - Validation: invalid styling follows
:user-invalidandaria-invalid="true"; always pair it with a.field__errormessage. Valid fields are not recoloured. - Hit area:
--smtext controls are 32px, and a field can’t take a hit-area buffer, so that is also its target. Put touch-facing forms infine:ui-compactrather than using--smdirectly. Put checkboxes and radios in a.choicelabel. - Range fill:
--valuedrives the fill in Blink and WebKit, so a script must update it oninput.
Keyboard and screen readers
The browser provides the keys, so don’t add key handlers. The exact keys for a Select differ by browser and platform.
- Tab moves from control to control, in reading order.
- A Select opens with Space or the arrow keys, the arrow keys move through it, Enter chooses and Escape closes.
- A Checkbox toggles with Space. So does a Switch, which is a Checkbox with
role="switch", and a screen reader announces it as on or off. - A group of radios with one
nameis one Tab stop. The arrow keys move and select within it. - A Range moves with the arrow keys, by larger steps with Page Up and Page Down, and to its ends with Home and End.
- An
<output>is a live region, so a screen reader announces a Range’s new value as it changes. - Errors and hints are read with the control through
aria-describedby. The browser never setsaria-invalid: server-rendered or scripted errors must set it themselves.
Say What’s Wrong Beside the Field
An error colour says something is wrong, and the message says what. The icon and the words mean it never relies on colour alone.
Account ID must start with "DFH-" followed by 6 digits.
Code
<div class="field">
<label for="account-id" class="field__label label">Account ID</label>
<p id="account-id-error" class="field__error small-text"><svg class="icon" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#Warning"/></svg>Account ID must start with "DFH-" followed by 6 digits.</p>
<input id="account-id" type="text" class="input" aria-invalid="true" aria-describedby="account-id-error" value="INVALID-ID" />
</div>
Put Checkboxes and Radios in a Label
A Checkbox or radio goes in a .choice label, so the whole row is the target; a set of them is .choices in a Fieldset.
Code
<label class="choice">
<input type="checkbox" class="checkbox-input" name="terms" />
<span>I agree to the plot terms</span>
</label>
<!-- Large box -->
<label class="choice">
<input type="radio" class="radio-input radio-input--lg" name="size" value="full" />
<span>Full plot</span>
</label>
A Switch Acts at Once
Use a Switch when flipping it changes the setting straight away. If nothing happens until someone submits a form, it’s a Checkbox.
Code
<label class="switch">
<span class="switch__label label">Notifications <span class="small-text text-muted">Email and push</span></span>
<input type="checkbox" role="switch" class="sr-only" checked />
<span class="switch__control" aria-hidden="true">
<span class="switch__option switch__option--off">Off</span>
<span class="switch__option switch__option--on">On</span>
</span>
</label>
<!-- Small, round, danger -->
<label class="switch switch--sm switch--round switch--danger">
<span class="switch__label label">Auto-delete</span>
<input type="checkbox" role="switch" class="sr-only" />
<span class="switch__control" aria-hidden="true">
<span class="switch__option switch__option--off">Off</span>
<span class="switch__option switch__option--on">On</span>
</span>
</label>
Show a Range’s Value
A slider shows roughly where it is, and an <output> beside it gives the number.
Code
<div class="field">
<div class="field__header">
<label for="volume-control" class="field__label label">Volume</label>
<output for="volume-control" class="small-text">65%</output>
</div>
<input id="volume-control" type="range" class="range" min="0" max="100" value="65" style="--value: 65%;" />
</div>
Do and Don’t
| Do | Don’t |
|---|---|
| Give every control a visible label. | Rely on a placeholder as the label. |
Use :user-invalid or aria-invalid="true" with a .field__error. | Show an error colour with no message. |
Set the right type and autocomplete. | Make people retype what the browser already knows. |
Put checkboxes and radios in a .choice label. | Leave a 20px box as the whole target. |
Use .ui-compact on a dense region. | Add --sm to every control in it. |
In Storybook
- Input variants and states
- Select variants and states
- Textarea variants and states
- Checkbox and Radio variants and states
- Switch variants and states
- Range variants and states
- Input tokens
- Select tokens
- Textarea tokens
- Checkbox and Radio tokens
- Switch tokens
- Range tokens
- Range Lockup tokens
See also
- Field, Fieldset and Form: What goes around form controls, such as labels, hints, errors, groups of choices and the Form's actions.
- Button: An action on the page, such as saving a Form, opening a Dialog or changing a setting.
- Combobox: A text field that filters a list of options, for single select or multi-select with Chips.