interaction
Every component answers people in the same way. The same state looks the same everywhere, the keyboard focus is always visible and lands where people expect it, and the keys do the same thing in every component. Once people have learned one component, they already know how the others behave.
This page describes what all components share. Each component's own page adds what is particular to it.
states
A control changes its look to show what is happening to it. These states look the same in every component:
| state | how it shows | where |
|---|---|---|
| Hover | A step brighter. A standard button's border turns blue | Buttons, icon buttons, table rows, tiles, menu items |
| Pressed | A step darker. The emphasized button turns a deeper blue | Buttons and icon buttons |
| Keyboard focus | An outline around the control | Every control; see focus |
| Selected or current | A blue tint and a blue bar on the leading edge; a blue line under the current tab; a filled box | Table rows, tabs, filter tabs, tiles, checkboxes |
| Open | The small arrow turns | Banner details, multi select, table groups, menus |
| Busy | A spinner in the control, or a line running along the top of a list | Buttons, menu buttons, the data table |
| Invalid | A red border, and the error in words under the field | Text fields, selects, text areas |
| Disabled | Dimmed, a “not allowed” pointer, and skipped by the keyboard | Every control |
- Never colour alone. A selected row also has a ticked checkbox, a field with an error also shows the error in words, and the current tab also has its underline. People who don't see the colour still get the message.
- A disabled control can't explain itself. The keyboard skips it, so someone who uses a keyboard or a screen reader may never find it. When the reason matters, such as “nothing selected”, say it next to the control, or keep the control enabled and explain when it is pressed; forms and settings shows how.
- Busy keeps its size. A busy button keeps its width, and a list that refreshes keeps its rows in place under the running line, so nothing on the page jumps.
focus
People who use the keyboard need to see where they are. Buttons, icon buttons, tabs, filter tabs, checkboxes, switches, sort buttons and disclosures show a clear outline when the keyboard brings focus to them. It stands out strongly against every background. A click with the mouse doesn't show it, because the pointer already shows where people are.
A focused menu item shows a bar on its leading edge and a lighter fill instead. Table rows draw the outline inside their cells, because browsers don't draw outlines on table rows reliably.
Today: two older families look different. Text fields, selects and text areas show focus with a blue border and a soft inner shadow, and the tiles on the home page with a blue frame.
where focus goes
- It never moves without a reason. Toasts never take it, and updates to the page never pull it away.
- It comes back. A menu or a dialog gives focus back to whatever opened it.
- It stays put when the control that had it disappears. After “clear”, or an action on a selection, the selection toolbar keeps focus; when the focused row leaves a list, the table's tab stop takes it.
- It goes to the problem. After a save that found errors, it moves to the first field that needs fixing; see forms and settings.
the keyboard model
Everything can be reached and used without a mouse. Wherever the browser has a control of its own, such as a button, a checkbox, a select or a disclosure, the components use it, so it behaves the way people already know.
- tab moves between components, and the arrow keys move inside one. A component made of several parts, such as a row of tabs, a toolbar or a table, is a single tab stop. That keeps the way through a page short.
- enter and space act, just as they do on the browser's own controls.
- esc closes the innermost thing that is open and gives focus back to what opened it. In a dialog with an open menu, the first press closes the menu, and the next one the dialog.
- Dialogs keep tab inside them until they close.
This is how the components that take one tab stop use the arrow keys:
| component | one tab stop, then |
|---|---|
| Tabs, filter tabs | The left and right arrow keys move and select; home and end jump to the first and the last |
| Toolbar | The left and right arrow keys move between its controls and wrap around; home and end |
| Data table | The up and down arrow keys move by row; home, end, page up and page down; the left and right arrow keys close and open groups; tab goes into the focused row's controls |
| The home page and tile groups | A grid: right and left follow the reading order, down and up follow the tiles' places on the screen; home and end |
| Action menu | Opens with enter, space or the down arrow key, or on its last item with the up arrow key; the arrow keys move; esc closes it |
Each component's exact keys are in the keyboard table on its React page. When you mention keys in text, show them as keycaps with the kbd component, like ctrl a.
selection
Wherever rows can be selected, it works the same way: a checkbox on every row, space to select the focused row, shift for a range, ctrl + a (or cmd + a) for every visible row, and one emphasized action for the whole selection, in the toolbar above.
Opening a row and selecting it are kept apart: a press on the row opens it, and its checkbox selects it. One click never does both. The whole model is in selection and bulk actions.
pointer and touch
- Targets are big enough to hit, at least 24 pixels square (WCAG 2.5.8). Even the smallest controls are taller than that, the whole label of a checkbox or a switch counts as its target, and so does a whole tile.
- Hover is never the only way in. What the pointer finds on hover, the keyboard finds on focus: tooltips, and the arrow on a tile.
- Nothing depends on a gesture. No swipe, drag or long press is the only way to do something.
related
- Accessibility: names, roles, announcements and the known gaps.
- Motion: how long a change of state takes.
- Selection and bulk actions: selecting rows and acting on them.
specs
| state | tokens and values |
|---|---|
| Hover | --surface-2; a --cloud-blue border on a default button |
| Pressed | --bg-2; a darker blue on a primary button |
| Keyboard focus | A 2px --focus-ring outline on :focus-visible, at least 6:1 against every background. A focused menu item: a 2px --focus-ring bar on its leading edge and a fill. Table rows: the same ring drawn on their cells |
| Selected or current | --surface-selected with a 2px --cloud-blue rule on the leading edge; a blue underline on the current tab; a filled box |
| Open | The caret turns; aria-expanded is set |
| Busy | A spinner in the control, or a running rule over a list; aria-busy is set |
| Invalid | A --state-error border, and the error text under the field |
| Disabled | 40–50% opacity, cursor: not-allowed, out of the tab order |
- Today: text fields, selects and textareas show focus as a
--cloud-blueborder with an inset shadow; the tiles on the home page as a--cloud-blueframe. - Native elements wherever they exist:
<button>, checkboxes,<select>,<details>. - Targets: at least 24px (WCAG 2.5.8). The
smcontrols are 28px high. - Keys in text: the
Kbdcomponent.