Accessibility Patterns
Every interactive pattern here is a contract: a set of keys that must work, a focus order that must be managed, and a set of things that must be announced. Written as a spec rather than a reminder, because “remember accessibility” has never once told anyone what to implement.
Each pattern carries the way it is usually broken, and that field is the most useful one on the page. These are not exotic mistakes — they are what happens when a component is built from what it looks like rather than from what it must do, and an automated checker passes almost all of them. A keyboard and thirty seconds catches more than any linter here.
semantics <button type="button"> — implicit role button, accessible name from its text content, disabled exposed as a state rather than only as a colour.
| Tab / Shift+Tab | Moves focus to and from the control in DOM order, with no tabindex needed. |
| Enter | Dispatches a click event and runs the activation behaviour. |
| Space | Also dispatches a click — on key-up, which is why holding Space does not repeat-fire. |
| Escape | Nothing here, which is correct: the button does not swallow a key the surrounding dialog or menu may need. |
- — Focusable by default; removed from the focus order automatically when
disabled. - — Draws a platform focus indicator that respects the user's contrast and colour settings, and matches
:focus-visibleonly when the user is navigating by keyboard. - — Focus does not move on click in every engine — do not build behaviour that assumes it did.
- — Role and name together: the equivalent of "Save, button".
- — Disabled state, without needing
aria-disabled. - — Appears in the screen reader's list of buttons, and under the swipe-by-control-type gestures on mobile.
usually broken by Rebuilding this on a div and stopping at role="button" plus a click handler. That produces a control that is announced as a button, is not in the tab order, and cannot be activated by any key at all — a defect that automated scanners often report as a pass because a role and a name are both present.
How to check it in two minutes
In this order. Each step catches a different class of failure.
- 1Put the mouse down and reach the pattern with Tab alone. If you cannot reach it, nothing below matters.
- 2Operate it with the keys listed above. A key that does nothing is a missing behaviour, not a preference.
- 3Watch where focus goes when it opens and — the one everybody forgets — where focus goes when it closes.
- 4Turn on a screen reader and confirm what is announced matches the announcements listed. Silence is a bug with no error message.
- 5Re-read the way this pattern is usually broken, above, and check that specific thing on purpose.