Designing Accessible Interfaces
Accessibility has a reputation problem. Too many designers hear the word and picture a compliance checklist bolted onto a finished design a week before launch — a scramble of red error text and ARIA labels that nobody really understands. That framing guarantees bad work. Accessibility is not a coat of paint you apply at the end; it is a set of constraints you design against from the first wireframe, exactly like a grid or a type scale. Do it that way and it costs you almost nothing extra. Skip it and you will spend three times as long retrofitting it later, and you will still ship something broken.
This guide covers the fundamentals that catch the majority of real-world problems: contrast, focus, keyboard flow, target size, semantics and text scaling. It ends with how to test, because accessibility is only real when it has been verified rather than assumed.
Start with contrast, because it is measurable
Contrast is the easiest accessibility requirement to get right because it is a number, not an opinion. The widely referenced standard defines thresholds based on the ratio between the relative luminance of the foreground and background colours. The critical numbers are these:
- 4.5:1 for normal body text (below roughly 18pt regular or 14pt bold).
- 3:1 for large text — 18pt regular and above, or 14pt bold and above.
- 3:1 for the visual boundary of user-interface components: input borders, icons that carry meaning, focus indicators, toggle tracks, chart lines.
The most common failure I see is placeholder text and disabled states. Designers reach for a 30% grey because it "looks subtle", and it lands around 2:1 — invisible to a huge share of users. Disabled controls are technically exempt from contrast rules, but if a control needs to be disabled you should still make it readable, otherwise the user cannot tell what they are missing.
Test contrast in your design tool or a browser inspector, but do it while the palette is still a set of tokens, not after you have painted forty screens. If your grey ramp is built so that "muted" text sits at 4.6:1 against the default surface, every muted label downstream inherits the fix for free.
Never rely on colour alone to communicate state. A red border with no icon, no text and no pattern fails around 1 in 12 men. Pair colour with a second cue — an icon, a label, a weight change — every single time.
Focus must be visible, always
Keyboard users navigate by tabbing, and if the focus indicator is invisible they are lost. The default browser outline is ugly, which is why teams remove it — and that single line of CSS is one of the most damaging accessibility bugs in existence. Do not remove focus styles. Redesign them.
A good focus ring:
- Has at least a 3:1 contrast ratio against the adjacent background.
- Is at least 2px thick, ideally with an offset so it hugs the shape rather than merging into it.
- Appears on
:focus-visibleso mouse users do not see it, but keyboard users always do. - Is never clipped by
overflow:hiddenon a parent container.
Tab through your own design as a mouse-free exercise. You should be able to reach every interactive element, see clearly where you are, and never get trapped in a loop you cannot escape. A common trap is a modal that does not trap focus properly, or a custom dropdown that swallows the Tab key.
Touch targets and spacing
Fingers are not cursors. The accepted minimum target size is 44×44 px (roughly 44 logical pixels on any platform), and that is a floor, not a goal — comfortable targets are larger. Apple's guidance says 44pt; Android says 48dp; the web standard suggests 24px as an absolute minimum with 44px recommended. Design to the largest of these and you will not go wrong.
Spacing matters as much as size. Two 44px buttons jammed together are effectively one 88px target with a confusing middle. Leave at least 8px of dead space between adjacent targets, and more on touch-first layouts. Icon-only buttons in a toolbar are the classic offender: shrink them to 32px to fit the row, and suddenly every tap is a coin-flip.
A quick target-size reference
| Element | Minimum size | Recommended |
|---|---|---|
| Primary button | 44×44 px | 48×48 px |
| Icon-only control | 44×44 px | 48×48 px |
| Checkbox / radio hit area | 44×44 px | 48×48 px |
| Inline text link | 24×24 px | 32×32 px |
| Table row (tappable) | 44 px tall | 56 px tall |
Text that survives zoom and user settings
Users enlarge text for dozens of reasons: ageing eyes, tiredness, a cracked screen, a bright train carriage. If your layout collapses when text reaches 200%, it is broken. Test by setting the browser zoom to 200% and by changing the base font size — both exist for real people.
Two rules keep you safe. First, use relative units (rem, em) for type and spacing, not fixed pixels for everything. Second, never set a container to a fixed height that assumes one line of text; use min-height so growth pushes rather than clips. Truncation via ellipsis is fine for a preview, but a title that hides the last word of an error message is a usability failure.
Semantics before ARIA
The cheapest accessibility win in the world is using the right HTML element. A real <button> is focusable, keyboard-operable, announced correctly and works when JavaScript fails. A <div> with a click handler is none of those things, and patching it with attributes is more work than just using a button. Reach for native elements first; only add roles and attributes when no native element fits.
This matters because assistive technology builds its model of your interface from these semantics. Give every image that carries meaning an alternative text description, and leave purely decorative images without one. Give every form field a visible, programmatically linked label — placeholder text is not a label, it vanishes the moment someone starts typing.
Motion, sound and flashing
Animation is where accessibility quietly disappears. Some users experience motion sickness or vestibular discomfort from large parallax scrolls, zooming transitions and spinning loaders. Respect the operating system's "reduce motion" preference: when it is on, replace movement with gentle fades and keep the interface usable without the animation. Avoid anything that flashes more than three times per second, as rapid flashing can trigger seizures.
How to test without guessing
You cannot verify accessibility from a screenshot. Three cheap tests catch most issues:
- Keyboard-only pass. Put the mouse away and complete your core task using only Tab, Shift+Tab, Enter and arrow keys.
- Zoom pass. Set the browser to 200% zoom and re-run the same task.
- Screen-reader pass. Turn on the built-in screen reader on your own machine and listen to how your interface is announced. It is humbling and fast.
Automated checkers are useful for contrast and missing labels, but they catch only a fraction of problems. They will never tell you that your error message is polite and useless, or that your tab order jumps across the page. Human judgement is the actual tool.
Key takeaways
- Aim for a 4.5:1 contrast ratio on body text and 3:1 on large text, UI borders and focus rings.
- Never remove focus indicators — redesign them, and use
:focus-visibleso both keyboard and mouse users are served. - Make touch targets at least 44×44 px with clear spacing between them.
- Use relative units and flexible containers so text at 200% zoom does not break the layout.
- Prefer native HTML elements over attribute-patched divs — free semantics, free keyboard support.
- Verify with a keyboard pass, a 200% zoom pass and a real screen reader, not a screenshot.