How would you ensure accessibility (e.g., screen readers)
Build on semantic HTML so the screen reader gets role/name/state for free, ensure full keyboard operability, give every control an accessible name, manage focus on dynamic changes, announce async updates with aria-live, and verify by actually navigating with VoiceOver/NVDA — not just running axe.
Ensuring accessibility — especially for screen readers — comes down to making sure every element exposes a correct name, role, and state, and that the whole app is operable without a mouse.
1. Semantic HTML is the foundation
A screen reader builds its model from the accessibility tree, which the browser derives from your HTML. Native elements populate it for free:
<button>Delete</button>
<!-- announced: "Delete, button" — focusable, Enter/Space work -->
<div class="button" onclick="...">Delete</div>
<!-- announced: "Delete" — not focusable, no role, invisible to SR users -->Use real <button>, <a href>, <input>, <nav>, <main>, headings in order, lists for lists, <table> for tabular data. This alone solves most problems.
2. Accessible names for everything
Every interactive element must have a name:
<label for="email">Email</label><input id="email" type="email" />
<button aria-label="Close dialog">✕</button>
<img src="chart.png" alt="Revenue grew 20% in Q3" />
<img src="divider.png" alt="" /> <!-- decorative: empty alt -->3. Full keyboard operability
If you can't reach and use it with Tab / Shift+Tab / Enter / Space / Arrows / Escape, a screen reader user can't either.
- Logical tab order (follow DOM order, avoid positive
tabindex). - Visible focus indicator — never
outline: nonewithout a replacement. - No keyboard traps; Escape closes overlays.
- Custom widgets follow ARIA Authoring Practices key patterns.
4. ARIA to fill the gaps
When no native element fits, ARIA describes role/state — but you must add the behavior:
<div role="tablist">
<button role="tab" aria-selected="true" aria-controls="panel-1">Profile</button>
</div>
<div role="tabpanel" id="panel-1">...</div>5. Announce dynamic changes with live regions
SPAs change content without a page reload — screen readers won't notice unless you tell them:
<div aria-live="polite">3 results found</div>
<div role="alert">Payment failed — try again</div> <!-- assertive -->polite waits for a pause; assertive/role="alert" interrupts. Use assertive only for errors.
6. Manage focus
- On route change in an SPA, move focus to the new page's
<h1>(or a skip target). - When a modal opens, focus moves into it and is trapped; on close, focus returns to the trigger.
- Never let focus land on hidden elements.
7. Test with a real screen reader
Automated tools (axe, Lighthouse) catch only ~30–40%. The rest needs:
- VoiceOver (Cmd+F5 on Mac), NVDA (free, Windows), TalkBack (Android).
- Navigate by headings, landmarks, and form controls — the way real users do.
- Tab through every flow with no mouse.
Senior framing
The strong answer frames screen-reader support as a byproduct of correct semantics and keyboard support, not a separate "ARIA layer" bolted on. Call out the accessibility-tree mental model, the name/role/state triad, focus management in SPAs, and that you've actually used VoiceOver/NVDA — that last point separates people who've done it from people who've read about it.
Follow-up questions
- •What is the accessibility tree and how is it built?
- •When do you use aria-live='polite' vs 'assertive'?
- •How do you handle focus on route changes in a SPA?
- •What's the difference between aria-label, aria-labelledby, and aria-describedby?
Common mistakes
- •Using div/span with click handlers instead of button/a.
- •Adding ARIA roles but no keyboard behavior.
- •Forgetting that SPA route changes are silent to screen readers.
- •Testing only with axe and never with an actual screen reader.
- •Putting aria-live on an element that's added to the DOM at the same time as the message (it must already exist).
Edge cases
- •aria-live regions must be present in the DOM before the content changes, or the change isn't announced.
- •Visually hidden text (.sr-only) must not use display:none — that removes it from the accessibility tree.
- •Toasts that auto-dismiss may vanish before a screen reader user reaches them.
Real-world examples
- •Skip-to-content links, accessible form validation, and live cart-count updates in e-commerce.