Skip to main content
Back to the notebook

Accessibility

Accessible by default: how I bake it in from the first sketch

Accessibility isn’t a checklist at the end — it’s a posture. Here’s how I keep it present from wireframe through to production code.

I work on public services. Accessibility there isn’t a nice-to-have or a backlog line item — it’s a legal obligation under the Public Sector Bodies Accessibility Regulations, and more importantly, it’s the difference between a service that works for everyone and one that quietly excludes people.

But "accessibility" as a final-stage audit is the most expensive, least effective way to do it. You find problems late, when they’re embedded in design decisions and component APIs that are hard to unwind. So I bake it in from the first sketch. Here’s what that actually looks like in practice.

Wireframes: start with the screen reader

Before I open Figma, I think about the page as a linear document. What does a screen reader user hear when they tab through it? That ordering drives the DOM order, which drives the visual layout — not the other way around.

In practice that means:

  • The page has one H1. Always. Everything else is an H2 or below.
  • The skip link is the first focusable element — in the layout component from the start, never retrofitted.
  • Sections have headings, even visually hidden ones, so heading navigation has landmarks to land on.
  • Interactive elements are real buttons or real links, not styled divs. If it has an onClick, it needs a role, a tabindex, and keyboard handlers — or it needs to just be a button.

I sketch the visual layout on top of that structure, not the reverse. If a design decision breaks the semantic order, the design changes.

Design tokens: contrast is a system, not a manual check

I define colour tokens once, and I define them to pass WCAG 2.2 AA:

  • Body text on its background meets 4.5:1 minimum.
  • Large text (18pt+ or 14pt+ bold) meets 3:1.
  • Interactive elements and focus indicators meet 3:1 against adjacent colours.
  • Muted labels and metadata meet 4.5:1 — no opacity tricks that drop text below the threshold.

This site’s own Tailwind config uses opacity modifiers like text-ink/60, and I’ve audited every one against its background. If a token doesn’t pass, it doesn’t ship — I’d rather darken a muted label than ship a contrast failure.

The payoff: I never manually check contrast on a finished page. The system handles it.

Components: build the constrained version first

When I build a component, I start with the most constrained version and layer up:

  1. Keyboard only. Tab to it, activate with Enter/Space, dismiss with Escape. If it’s a modal, focus is trapped and returned on close.
  2. Screen reader. Roles, labels, live regions for dynamic content, aria-expanded/aria-controls for toggles.
  3. 200% zoom. Text reflows, no horizontal scroll, no overlapping elements.
  4. Reduced motion. Animations collapse to instant or near-instant under prefers-reduced-motion.

Only after all four pass do I add the polish — hover states, motion, decorative layering — and decoration is always aria-hidden so it never pollutes the assistive tech experience.

The audit that wasn’t needed

I ran axe-core against this portfolio after launch. Zero violations. Not because I’m perfect — because the decisions that prevent violations were made at the token, component, and layout level before any code was written.

That’s what "accessible by default" means. Not that you’ll never find a bug. But that the system makes the accessible path the easy path, and the inaccessible path the one that requires extra work.

The checklist I do use

I don’t have a final-stage accessibility checklist. I have a starting-stage one:

That list runs before I write the component, not after.

Keep reading

More from the blog.

Product

Shipping solo: what I learned building three live products

Horrorometer, Kanaboards, and SENDadvisor all started as itches I wanted to scratch. Some patterns held across all three — and a few surprised me.

8 min read
UX

Designing for stressful systems: lessons from SENDadvisor

Families navigating SEND services are already overwhelmed. How do you design a directory that feels calm, trustworthy, and genuinely useful?

7 min read
AI

Agents on the board: planning with coding agents as teammates

Kanaboards started with a question: what if AI coding agents could read and update the same planning surface as the humans? Here’s what that looks like.

5 min read