Accessibility as a design input

What it is

Accessibility means the interface works for people using it in ways you did not personally test: with a screen reader, with a keyboard only, at 400 percent zoom, with a switch device, with low vision, with a tremor, with a cognitive load you do not have, or on a phone in bright sunlight with one hand.

As a design input means the decision is made when the component is chosen, not when the audit report arrives. The distinction is concrete and it is about cost:

Design input:    "This is a single-select from 8 known options, so it
                 is a <select> or a radio group."
                 Cost: zero. Keyboard, screen reader, mobile picker,
                 form association and focus all work.

Launch gate:     "The custom dropdown fails the audit."
                 Cost: reimplement arrow keys, Home/End, typeahead,
                 Escape, focus return, aria-activedescendant, the
                 listbox/option role pairing, and virtual-cursor
                 behaviour across three screen readers.
                 Then maintain it.

What this is confused with: accessibility as a compliance checklist run before release. A checklist finds contrast failures and missing alt text, which are cheap to fix. It cannot fix a custom control that should never have been custom, or an information architecture that only makes sense visually. The expensive failures are architectural, and a gate catches them after the cost is sunk.

Also confused: ARIA as the accessibility tool. ARIA changes what assistive technology reports. It adds no behaviour: role="button" on a <div> does not make Enter or Space activate it, does not make it focusable, and does not give it a disabled state.

The problem it solves

Two problems, one about people and one about cost, and the second is what gets it funded.

People. Roughly one in five adults reports a disability, and the interfaces they use are the same ones everyone else uses. Most accessibility improvements are unconditional usability improvements: captions get used in noisy rooms, keyboard navigation is what power users want, sufficient contrast is what everyone needs on a phone outdoors, and clear error messages help everyone.

Cost, and this is the argument that works internally. The remediation cost curve is steep:

Cost to fix "this listbox is not keyboard operable"

  at component-selection time    ~0        (use a <select>)
  during implementation          ~1 day    (adopt a headless library)
  at code review                 ~2 days   (rework, retest)
  at pre-launch audit          ~2 weeks    (rework + regression + retest,
                                            plus a launch decision)
  after a legal complaint     ~months      (remediation plan, external
                                            audit, plus the complaint)

And the failure is not evenly distributed across the codebase. WebAIM's annual analysis of the top million home pages consistently finds that around 95 percent have detectable WCAG failures, with a small number of error types (low contrast text, missing alt text, missing form labels, empty links and buttons) accounting for the large majority. These are all decisions, not bugs, which is the argument for moving them upstream.

Legal exposure is jurisdiction-specific and real. In Ontario, the AODA's Integrated Accessibility Standards Regulation requires designated public sector organisations and private organisations with 50 or more employees to meet WCAG 2.0 Level AA for their websites. The European Accessibility Act applies to a broad set of consumer digital services. In the US, ADA Title III web accessibility litigation runs into the thousands of filings a year. For a Toronto-based product with 50-plus employees, WCAG 2.0 AA is a legal floor, not an aspiration.

Mechanics

The order of operations

1. Semantic HTML first. This is the highest-leverage rule and it is nearly free.

<!-- Free: focusable, Enter and Space activate it, announced as
     "button", supports disabled, works with voice control by name. -->
<button type="button" onclick="save()">Save</button>

<!-- Broken: not focusable, no keyboard activation, announced as
     nothing, invisible to voice control. -->
<div class="btn" onclick="save()">Save</div>

<!-- "Fixed", and now you own four behaviours forever. -->
<div class="btn" role="button" tabindex="0"
     onclick="save()"
     onkeydown="if(e.key==='Enter'||e.key===' '){e.preventDefault();save()}">
  Save
</div>

The first rule of ARIA is not to use ARIA when a native element will do, which is stated in the ARIA Authoring Practices themselves. Native elements carry role, state, keyboard behaviour, focus management and platform conventions (a <select> becomes a native picker on iOS) that you would otherwise reimplement.

2. Keyboard operability, including focus management. Every interactive element must be reachable and operable by keyboard, and focus must never be lost.

The four focus rules that cover most failures:

1. VISIBLE. Never `outline: none` without a replacement. Use
   :focus-visible so mouse users do not see rings but keyboard
   users do.
2. ORDER matches visual order. CSS that reorders (flex `order`,
   grid placement) desynchronises tab order from what is on screen.
3. TRAPPED in a modal, and RETURNED on close. Open a dialog: focus
   moves into it and cannot leave. Close it: focus returns to the
   element that opened it, or the page becomes unnavigable.
4. MANAGED on route change. A client-side route change does not move
   focus, so a screen reader user stays where they were while the
   page silently replaces itself. Move focus to the new page's
   heading (with tabindex="-1") and announce the change.

Route-change focus is the single most common SPA-specific accessibility bug, because it does not exist in a multi-page app and no static analyser detects it.

3. Names, roles and values. Every control needs an accessible name.

<!-- Programmatically associated. The label is also a click target. -->
<label for="email">Email address</label>
<input id="email" type="email"
       aria-describedby="email-hint email-error"
       aria-invalid="true" />
<p id="email-hint">We only use this for order updates.</p>
<p id="email-error" role="alert">Enter an email address with an @ sign.</p>

<!-- Icon-only button: the name comes from aria-label, and the icon
     is hidden so it is not announced twice. -->
<button aria-label="Delete draft">
  <svg aria-hidden="true" focusable="false">...</svg>
</button>

aria-label overrides the visible text in the accessible name, which breaks voice control: a user saying "click Submit" cannot activate a button labelled aria-label="Send form" that displays "Submit". When there is visible text, let it be the name.

4. Contrast and target size, which are the two numeric criteria worth memorising:

WCAG 2.2 AA:
  1.4.3  text contrast          4.5:1
         large text (>=24px, or >=18.66px bold)   3:1
  1.4.11 non-text contrast (UI component
         boundaries, icons, focus indicators)     3:1
  2.5.8  target size (minimum)  24x24 CSS px, with exceptions for
         inline links and spacing
  1.4.4  text resizes to 200% without loss of content or function
  1.4.10 reflow: usable at 320 CSS px wide without 2D scrolling

The 1.4.11 non-text rule catches the fashionable failure: a 1px light-grey border on a white input is a 1.4:1 contrast ratio, so the field boundary is invisible to a large group of users.

5. Dynamic content announcements. Screen readers do not notice DOM changes unless told.

<!-- Polite: announced when the user is idle. For status updates. -->
<div aria-live="polite" aria-atomic="true">3 results found</div>

<!-- Assertive: interrupts. For errors only. role="alert" implies it. -->
<div role="alert">Payment failed. Your card was not charged.</div>

Over-announcing is a real failure mode. A live region wrapping a rapidly updating value floods the user with speech and makes the page unusable. Announce meaningful state changes, not every render.

6. Motion and preferences.

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

Vestibular disorders make large parallax and motion genuinely nauseating, and this is three lines.

Where automated testing stops

axe-core / Lighthouse catch:
  missing alt, missing labels, low text contrast, duplicate ids,
  invalid ARIA attribute values, missing document language,
  empty buttons and links

They CANNOT catch:
  - alt text that exists and is wrong ("image123.jpg")
  - a focus order that is technically valid and nonsensical
  - a custom widget with correct roles and broken keyboard behaviour
  - an error message that is announced but does not say what to fix
  - a heading structure that is visually clear and semantically flat
  - whether the workflow is actually completable

Deque, who maintain axe-core, report that automated testing finds a majority but not all of WCAG issues, with the remainder requiring human judgment. The practical policy is: automate the mechanical checks in CI so they never regress, and spend human testing time on keyboard-only and screen-reader walkthroughs of the critical flows.

// CI: catches regressions on the mechanical rules for free.
import { injectAxe, checkA11y } from 'axe-playwright'

test('checkout has no automatically detectable a11y violations', async ({ page }) => {
  await page.goto('/checkout')
  await injectAxe(page)
  await checkA11y(page, null, { detailedReport: true })
})
Manual pass, 20 minutes per critical flow:
  1. Unplug the mouse. Complete the flow. Note where focus goes and
     where it is lost.
  2. Turn on VoiceOver (Cmd+F5) or NVDA. Complete the flow with the
     screen off if you can stand it.
  3. Zoom to 400% at 1280px wide. Check nothing is cut off and
     nothing requires horizontal scrolling.

"Unplug the mouse" finds more real problems per minute than any tool, because keyboard operability is where custom components fail and it needs no expertise to evaluate.

A worked example: a filter panel that failed on one design decision

A search results page with a faceted filter panel. The design used a custom multi-select dropdown for each facet, built as <div>s to match the design system's visual style.

What the pre-launch audit found, and what each finding cost:

Finding                                        Severity   Fix cost
------------------------------------------------------------------
Facet dropdowns not keyboard operable          blocker    see below
Result count updates not announced             serious    2 hours
Filter chips (remove buttons) unlabelled       serious    1 hour
Focus lost when a filter removed the focused
  chip from the DOM                            serious    4 hours
Applied-filters region has no heading          moderate   30 min
Focus ring removed globally in the reset CSS   serious    1 hour
Facet checkbox labels 3.9:1 on white           moderate   30 min
Route change to a filtered URL did not move
  focus or announce                            serious    3 hours

Everything except the first was under a day in total. The first was the whole project.

What "keyboard operable" required for the custom dropdown:

Behaviours to implement to match the ARIA listbox pattern:
  - Enter/Space/Down opens, Escape closes and returns focus
  - Up/Down move the active option, Home/End jump to first/last
  - typeahead: typing "sh" jumps to "Shipping", with a reset timeout
  - aria-activedescendant tracking on the input, since focus stays
    on the combobox while the "active" option changes
  - role="listbox" / role="option" / aria-selected pairing
  - aria-expanded on the trigger
  - scroll the active option into view without stealing focus
  - virtual-cursor behaviour differences across VoiceOver, NVDA
    and JAWS, which is where the real time goes

Estimated: 2 weeks to build and test across three screen readers,
plus permanent maintenance.

What was actually done:

Replaced the custom dropdown with a disclosure button plus a fieldset
of native checkboxes.

<fieldset>
  <legend>Brand</legend>
  <label><input type="checkbox" name="brand" value="acme"> Acme (142)</label>
  ...
</fieldset>

Behaviours obtained for free: Tab and Space, screen reader
announcement of "Brand, group" and "Acme, checkbox, not checked, 1 of
8", native mobile behaviour, form association, and voice control by
visible label.

Implementation: 1.5 days including restyling.
Long-lived cost: none.

The two-week custom widget was replaced by a day and a half of native elements, and the visual difference was a design review conversation rather than an engineering project. The decision that cost two weeks was made in a design file, months before any code existed, which is the whole argument of this page.

The other findings, and the two that generalise:

Focus lost on chip removal:
  Removing the focused chip left focus on <body>, so a keyboard user
  was returned to the top of the document and had to tab back through
  the header every time they removed a filter.
  Fix: before removing, move focus to the next chip, or to the
  filter panel heading if it was the last one.
  RULE: whenever you remove the focused element, you must decide
  where focus goes. There is no sensible default.

Route change not announced:
  Applying a filter pushed a new URL and replaced the results. A
  screen reader user heard nothing and their virtual cursor stayed
  where it was.
  Fix: move focus to the results heading (tabindex="-1") and use a
  polite live region for "142 results for Brand: Acme".
  RULE: a client-side route change must move focus, because the
  browser is no longer doing it for you.

Both rules exist because SPAs removed a behaviour the browser used to provide, and neither is detectable by any linter.

The result, measured the way the team measured everything else:

                              before        after
axe violations (checkout +
  search flows)                  23             0
keyboard-only task completion
  (5 flows, internal test)      2 of 5       5 of 5
custom interactive widgets
  in the design system            7             2

Reducing custom widgets from seven to two was the durable outcome, because each one had been an independent, permanent maintenance liability, and each removal fixed the same eight behaviours at once.

Production evidence

The AODA's Integrated Accessibility Standards Regulation (Ontario Regulation 191/11) requires WCAG 2.0 Level AA conformance for the websites of designated public sector organisations and private organisations with 50 or more employees, which makes it a concrete legal floor for Toronto-based products rather than a guideline.

The European Accessibility Act extends accessibility requirements to a broad range of consumer products and digital services sold in the EU, on a timeline running from 2025, which is why multinational product teams have been retrofitting.

WebAIM's annual "WebAIM Million" analysis of the top million home pages consistently finds around 95 percent with detectable WCAG failures, and reports that a small number of error types (low contrast text, missing alt text, missing form labels, empty links and buttons) account for the large majority of detected errors.

The W3C's ARIA Authoring Practices Guide states the "first rule of ARIA use" explicitly: prefer a native element with the semantics and behaviour you need over repurposing an element and adding ARIA. It also publishes the full keyboard interaction specification for each pattern, which is the concrete answer to how much work a custom combobox is.

Deque, the maintainers of axe-core, publish the position that automated testing finds a majority of WCAG issues but not all, and that manual testing is required for the remainder. Their tooling is what most CI accessibility checks run.

GOV.UK's Design System is the most rigorously documented example of accessibility as a design input: each component publishes its research, its assistive-technology test results, and the rationale for preferring native elements.

The debate

Should accessibility be a launch gate at all? A gate is better than nothing and it is the wrong primary control, because it catches architectural decisions after the cost is sunk. The position: gate on automated checks in CI (cheap, mechanical, prevents regression), and move the expensive decisions into design review with one question: does this component already exist natively?

Is a design system enough? It is the highest-leverage investment, because fixing a component once fixes every use. It is not enough alone, because composition-level failures (focus order, route announcements, heading structure, error recovery) live between components and no design system can cover them.

Do you need to test with real assistive technology users? For anything with genuine complexity, yes, and the honest reason is that developers testing with VoiceOver test the way developers use VoiceOver, which is not how a daily user uses it. The pragmatic middle: automate the mechanical checks, do a keyboard-and-screen-reader pass per critical flow in-house, and budget user testing for the flows that matter most.

Is WCAG AAA the goal? No. AA is the standard that regulation, procurement and case law reference, and several AAA criteria (7:1 contrast, sign language interpretation) conflict with reasonable design or are impractical for most products. Aim at AA and exceed it where it is cheap.

Is overlay tooling (an accessibility widget script) a solution? No, and it is worth being blunt about this in an interview because it is a live question in many organisations. Overlays cannot fix semantics, focus management or keyboard operability, they frequently interfere with the user's own assistive technology, and disability advocacy organisations have publicly opposed them. They are a liability-shaped product, not a remediation.

Does accessibility slow delivery? As a design input, no, it usually speeds it up by removing custom components. As a retrofit, substantially yes, which is the same statement about cost from a different direction and is the argument to make when someone proposes deferring it.

Follow-up Q&A

"What does treating accessibility as a design input actually mean in practice?"

One question in design review: does a native element already do this? A single-select from a known list is a <select> or a radio group, and choosing that costs nothing while choosing a custom dropdown costs roughly two weeks to implement the ARIA listbox keyboard contract plus permanent maintenance across three screen readers. The decision is made in a design file months before code exists, which is why a pre-launch gate cannot help: by then the only options are ship it broken or rebuild it.

"Why is semantic HTML the highest-leverage rule?"

Because native elements carry role, state, keyboard behaviour, focus handling and platform conventions that you otherwise reimplement and maintain. A <button> is focusable, activates on Enter and Space, announces as a button, supports disabled, and is addressable by voice control using its visible label. A <div role="button" tabindex="0"> gives you the announcement and none of the behaviour, and every behaviour you add is code you own forever. The ARIA Authoring Practices state this as the first rule of ARIA.

"What accessibility bugs are specific to single-page applications?"

Two, and neither exists in a multi-page app. A client-side route change does not move focus or announce anything, so a screen reader user stays where they were while the page silently replaces itself: the fix is to move focus to the new heading with tabindex="-1" and use a polite live region for the change. And removing the currently focused element, a chip, a row, a closed dialog, drops focus to <body>, sending a keyboard user back to the top of the document: the fix is to decide explicitly where focus goes before removing it. No linter detects either.

"What can automated accessibility testing not catch?"

Anything requiring judgment: alt text that exists and is wrong, a focus order that is technically valid and nonsensical, a custom widget with correct roles and broken keyboard behaviour, an error message that is announced but does not say what to fix, a flat heading structure that looks hierarchical, and whether the task is completable at all. Deque, who maintain axe-core, report that automation finds a majority of issues and not all. The policy that follows: automate the mechanical rules in CI so they cannot regress, and spend human time on keyboard-only and screen-reader walkthroughs of critical flows.

"Which WCAG numbers should you know?"

Text contrast 4.5:1, and 3:1 for large text at 24px or 18.66px bold. Non-text contrast 3:1, which covers UI component boundaries, icons and focus indicators, and which is what a 1px light-grey border on a white input fails. Target size 24 by 24 CSS pixels minimum in WCAG 2.2. Text must resize to 200 percent, and content must reflow usably at 320 CSS pixels wide without two-dimensional scrolling. AA is the level that regulation and procurement reference.

"What is the legal position in Ontario?"

The AODA's Integrated Accessibility Standards Regulation requires WCAG 2.0 Level AA for the websites of designated public sector organisations and private organisations with 50 or more employees. So for a Toronto product past that headcount it is a legal floor rather than an aspiration. The European Accessibility Act extends comparable obligations to consumer digital services in the EU, and US ADA Title III web litigation runs to thousands of filings a year, so a product selling into multiple markets is usually subject to several at once.

Common misconceptions

"ARIA makes things accessible." ARIA changes what is reported to assistive technology. It adds no behaviour: role="button" does not make Enter activate anything.

"An automated scan proves compliance." It catches mechanical failures. It cannot evaluate whether alt text is correct, whether focus order makes sense, or whether the task can be completed.

"Accessibility is for a small minority." About one in five adults reports a disability, and most of the fixes are unconditional usability improvements: captions, keyboard navigation, contrast, clear errors.

"We will fix it in the accessibility sprint." The expensive failures are component and architecture choices. A sprint at the end can only rebuild them.

"An accessibility overlay widget solves this." It cannot fix semantics, focus or keyboard operability, it often interferes with the user's own assistive technology, and disability advocacy organisations have publicly opposed overlays.

"AAA is the target." AA is what regulation and procurement reference, and some AAA criteria are impractical or conflict with reasonable design.

Interview delivery note

Say this verbatim: "The expensive accessibility failures are component choices, not bugs. Choosing a custom dropdown over a native select is a two-week ARIA listbox implementation plus permanent maintenance, and that decision is made in a design file months before an audit can catch it. So the control that works is one question in design review: does a native element already do this?" It moves the conversation from compliance to cost, which is where a lead is expected to operate.

The senior-versus-staff separator is naming the two SPA-specific failures that no tool detects: a client-side route change that does not move focus or announce, and focus dropped to <body> when the focused element is removed. Both exist because single-page applications removed behaviour the browser used to provide, both are invisible to linters and to automated scans, and both are found in twenty seconds by unplugging the mouse. Knowing that the cheapest high-yield test is "unplug the mouse and complete the flow" signals you have actually done this.

The second signal is being direct about accessibility overlays, since it is a live procurement question in many organisations and the correct answer is unambiguous: they do not fix semantics, focus or keyboard operability, and advocacy organisations oppose them.

Further reading

  • W3C ARIA Authoring Practices Guide, particularly the first rule of ARIA and the full keyboard interaction specifications per pattern.
  • WCAG 2.2 quick reference, for the exact success criteria and the AA thresholds.
  • Ontario Regulation 191/11 (the AODA Integrated Accessibility Standards Regulation), for the WCAG 2.0 AA obligation and who it applies to.
  • WebAIM's annual "WebAIM Million" report, for the distribution of real-world failure types.
  • The GOV.UK Design System component pages, for accessibility research and assistive-technology test results published per component.