Product design and accessibility: a designer's checklist
Most accessibility bugs are designed in long before a developer touches them. Here's how to bake inclusion into your Figma file — and a checklist to run before handoff.
Accessibility isn't a QA pass at the end of a sprint — it's a series of decisions made in Figma, in research interviews, and in the half-second a PM picks a colour. By the time a ticket lands with engineering, the constraints are already set: contrast, hierarchy, hit areas, motion, copy. Designers own most of that surface area.
The good news: the highest-leverage accessibility work is also the cheapest. A button resized from 32px to 44px costs nothing in code. A colour token nudged from 3.9:1 to 4.6:1 costs nothing in code. Catching these in design saves the entire downstream cycle of audit, ticket, fix, regression-test.
The four design decisions that cause the most bugs
- 01Colour as the only signal — error states with red text and no icon, links distinguished only by hue, charts that lose meaning in greyscale.
- 02Contrast that looks fine on your Retina display but fails at 4.5:1 on a real laptop in a coffee shop.
- 03Touch targets under 44×44px, especially in dense tables, mobile nav bars, and icon-only buttons.
- 04Copy that assumes context — 'Click here', 'Learn more', 'Submit' — instead of describing the action.
Designing for assistive tech you can't see
Screen reader users navigate by headings, landmarks, and link text. If your design has six 18px bolded labels and no clear H1 / H2 / H3 structure, you've designed a page that is unnavigable by audio. Annotate heading levels in your Figma file. Annotate landmarks (header, nav, main, footer). Annotate the reading order when it diverges from the visual order — multi-column layouts and floating CTAs are the usual culprits.
Same goes for state. A toggle that animates from grey to blue is invisible to a screen reader unless someone writes aria-pressed. Designers should call out state changes in the spec — 'announce: filter applied, 24 results' — so engineering doesn't have to invent the copy.
The designer's pre-handoff checklist
Run this before you mark a Figma frame ready for dev. It takes about 15 minutes per screen and catches the majority of avoidable findings.
- Contrast: every text/background pair hits 4.5:1 (3:1 for ≥18px bold or ≥24px regular). Verified with the Stark or Able plugin, not eyeballed.
- Non-text contrast: icons, form borders, focus rings, and chart elements hit 3:1 against their adjacent colour.
- Colour independence: every status, link, chart series, and form error is distinguishable without colour (icon, label, pattern, or weight).
- Touch targets: every interactive element is ≥44×44px on mobile, ≥24×24px on desktop with adequate spacing.
- Focus order: reading order in the Figma layer panel matches the intended tab order. Annotated where it diverges from visual order.
- Heading structure: one H1 per screen, nested H2/H3 with no skipped levels. Annotated on the frame.
- Link & button copy: every CTA describes its destination or action out of context. No 'click here', no naked 'Learn more'.
- Form fields: visible label (not placeholder-only), visible required indicator, error copy that names the field and the fix.
- Motion: any auto-playing or looping motion has a reduced-motion variant. Parallax and auto-carousels have a pause control.
- Zoom & reflow: the layout still works at 200% zoom and at a 320px viewport. No horizontal scroll, no clipped content.
- Empty, loading, and error states designed for every data-driven component — not just the happy path.
- Alt text drafted for every meaningful image. Decorative images marked as such.
Build it into your design system
A checklist that lives in a Notion doc gets ignored. A checklist baked into your component library enforces itself. Bake minimum touch sizes into your button variants. Bake compliant contrast into your colour tokens (and delete the failing ones). Ship a focus-ring token. Ship a form-error pattern with the screen-reader copy already specified. Every designer who reaches for a primitive inherits the right defaults.