Semantics first, always
Most accessibility work is avoided rather than done. A native <button>
is focusable, activates on Enter and Space, announces its role, participates in forms
and respects the user's operating-system settings. A <div> with a
click handler does none of that, and getting it to requires a tabindex, a role, two key
handlers and a comment explaining why.
The same applies to landmarks. A page with header, nav,
main and footer is navigable by region on a screen reader
without any ARIA at all. Adding roles to elements that already have them is a common
and slightly sad form of effort.
Note
The first rule of ARIA is not to use ARIA where a native element will do. ARIA
changes what assistive technology reports; it does not change behaviour. An element
with role="button" that cannot be activated from the keyboard is worse
than an unlabelled div, because it lies.
Design breakpoints around content
Breakpoints named after devices age badly and were never accurate. Choose them where the content stops working: where a line of body text exceeds roughly seventy-five characters, where a two-column grid leaves an awkward orphan, where a navigation list no longer fits.
Expressing breakpoints in em rather than pixels means they respond to the
user's font-size preference, so someone browsing at 24px base text gets the simpler
layout that the extra size warrants.
Content-driven breakpoint
/* Mobile first: one column, no query needed. */
.grid { display: grid; gap: var(--grid-gap); }
/* Two columns only once each column can hold a sensible measure. */
@media (min-width: 48em) {
.grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
Never remove the focus indicator
Removing the outline because it is ugly is the single most damaging line of CSS in common circulation. If the default indicator does not suit the design, replace it with one that does — and check it against both the light and the dark surfaces it will appear on.
A visible, designed focus style
:focus-visible {
outline: 2px solid var(--colour-accent);
outline-offset: 3px;
}
:focus-visible shows the indicator for keyboard interaction and suppresses
it for pointer clicks, which removes the usual objection to visible focus without
removing the indicator itself.
Touch targets and pointer type
Small targets fail everyone, but they fail hardest for people with tremor or limited dexterity, and on a moving train. Aim for a comfortable minimum of around 44 CSS pixels in both dimensions for anything interactive, and increase spacing where the input is coarse.
Adapting to input, not to width
@media (pointer: coarse) {
.nav__link { padding-block: var(--space-sm); }
}
Respect reduced motion
Subtle entrance animation improves a page. For some users it causes nausea, migraine or genuine disorientation, and the operating system already knows who they are. Honour it.
Motion as an enhancement
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
Watch out
If content is hidden until an animation reveals it, disabling the animation must not leave the content hidden. Build reveal effects so that the visible state is the default and the hidden state is applied by script only when animation will actually run.
Test with the tools users have
Automated checks catch perhaps a third of real problems, and none of the ones that matter most. Add four manual passes to every release:
- Keyboard only. Tab through the whole page. Can you reach everything, can you see where you are, can you escape every overlay?
- 200% zoom. At 1280px wide, zoom to 200%. Nothing should be cut off and nothing should require horizontal scrolling.
- Screen reader. Read one full page with the reader your users are most likely to have. Headings should form a sensible outline on their own.
- Colour contrast. Check text, and also the focus indicator, the placeholder text and the disabled states everyone forgets.
| Content | Minimum ratio (AA) |
|---|---|
| Body text | 4.5 : 1 |
| Large text (24px, or 19px bold) | 3 : 1 |
| Interface components and focus indicators | 3 : 1 |
Accessibility is not a phase
Treating it as a stage before launch guarantees a list of expensive retrofits. Treating it as a property of each component — written into the contract, checked in review — means the list never accumulates. It is the same argument as testing, and it has the same answer.