Web Accessibility Interview Questions
Short answer
Accessibility interviews come down to four things: using native elements so behaviour comes free, making everything reachable and operable by keyboard, managing focus when the UI changes, and knowing that ARIA changes only what is announced and adds no behaviour at all.
| The question | What it reveals |
|---|---|
| What's the biggest a11y win? | Whether you know native elements carry behaviour |
| When would you use ARIA? | Seniority - the right answer is usually "I wouldn't" |
| Build an accessible modal | Real experience. Focus management cannot be bluffed |
| What is the accessibility tree? | Whether you have a model or a checklist |
| How do you announce a toast? | Live regions - commonly needed, rarely known |
| When is alt text empty? | Judgement, not a rule |
| What contrast ratio is required? | Whether a11y is a spec you have read |
| How do you test it? | Whether you know automation catches only a third |
How accessibility gets tested in an interview
Accessibility rarely gets its own round. It arrives as a follow-up inside a machine coding round, you have just built a modal or a dropdown, and the interviewer asks how you would make it usable with a keyboard and a screen reader. That framing matters, because it means the assessment is practical: they are watching whether you reach for a native element first, and whether focus goes somewhere sensible when your component opens and closes.
It is also the topic where a short, correct answer beats a long one. "Use semantic HTML" is right, and every candidate says it. What distinguishes a strong answer is being able to keep going when asked why, because a `button` is focusable, activates on both Enter and Space, exposes a disabled state, and is announced as a button, none of which a div with a click handler does. Four concrete behaviours beat one principle.
What is the accessibility tree?
A parallel structure the browser builds alongside the DOM, containing only what assistive technology needs: each node's role, its accessible name, its state and its value. A screen reader reads that tree, not your CSS. This is the model worth having, because every accessibility rule follows from it, a div has no role, so it does not appear as a control; a label gives an input its accessible name; `aria-hidden` removes a node from the tree without hiding it visually.
1<button>Save</button>
2<!-- → role: button, name: "Save", from the text content -->
3
4<button aria-label="Close dialog">×</button>
5<!-- → name: "Close dialog"; the × alone would announce as "times" -->
6
7<label for="q">Search</label><input id="q" />
8<!-- → the input's name is "Search", from the associated label -->
9
10<input placeholder="Search" />
11<!-- → name is unreliable and disappears once typing starts. Not a label. -->
12
13<div onclick="save()">Save</div>
14<!-- → no role, no name, not focusable: invisible to the tree as a control -->
15
16<span aria-hidden="true">🎉</span> Payment received
17<!-- → visible on screen, removed from the tree, so it is not read aloud -->
18
19<img src="/chart.png" alt="" />
20<!-- → empty alt removes it from the tree entirely: correct for decoration -->The name-calculation order is worth knowing for follow-ups: `aria-labelledby` wins, then `aria-label`, then the native source such as a `label` element or the button's own text, then `title` as a last resort. Being able to say that explains why an `aria-label` on a button silently overrides its visible text, a real bug, because a voice-control user says what they see and nothing happens.
What is the single biggest accessibility win?
Using the native element. A `button`, `a`, `input`, `select` and `details` each arrive with a role, a focusable state, keyboard handling and platform conventions already implemented and tested across every browser and screen reader. The large majority of accessibility bugs in real codebases come from re-implementing one of those with a div and then rebuilding a fraction of the behaviour by hand.
1<div role="button" tabindex="0" onclick="buy()">Buy</div>
2<!-- Still missing, all of which the native element gives you free: -->
3<!-- → activation on Space (Enter alone is not enough) -->
4<!-- → a disabled state that also removes it from the tab order -->
5<!-- → form submission, and the Enter-submits-the-form convention -->
6<!-- → the platform's own focus and active styling -->
7<!-- → correct behaviour in forced-colors and high-contrast modes -->
8
9<button type="button" onclick="buy()">Buy</button>
10<!-- → all of the above, in eight fewer characters -->
11
12<!-- The same trade in three other places -->
13<a href="/kits">Kits</a> <!-- vs a div with router.push -->
14<details><summary>More</summary>…</details> <!-- vs a custom accordion -->
15<input type="checkbox" /> <!-- vs a styled div with aria-checked -->The honest counterpoint is worth offering before you are asked: native elements are harder to style, which is the actual reason teams reach for divs. The answer is that `appearance: none` plus your own styles gets you almost anywhere while keeping the behaviour, and that the genuinely un-styleable cases, `select` being the classic, are the ones where a tested library beats hand-rolling.
How do you make an interface keyboard accessible?
Everything interactive must be reachable with Tab and operable with Enter, Space or the arrow keys, in an order that follows the visual layout, with a focus indicator you can see. Those four requirements cover most of what is asked, and each has a specific failure mode worth naming.
1/* Never do this. It removes the only cue a keyboard user has. */
2:focus { outline: none; }
3
4/* Do this: a visible ring for keyboard users, none for mouse clicks */
5:focus-visible {
6 outline: 2px solid var(--accent);
7 outline-offset: 2px;
8}
9/* → the browser decides which input method warrants the ring */
10
11/* A skip link: the first tabbable element, hidden until focused */
12.skip { position: absolute; left: -9999px; }
13.skip:focus { left: 1rem; top: 1rem; }
14/* → lets a keyboard user jump past 30 nav links to the main content */
15
16/* display:none and visibility:hidden remove an element from the tab order.
17 opacity:0 does NOT, an invisible menu stays fully tabbable. */
18.menu-closed { opacity: 0; } /* → still focusable: a real bug */
19.menu-closed { display: none; } /* → correctly out of the tab order */
20
21/* tabindex: 0 puts an element in the natural order, -1 makes it
22 focusable only by script. A positive value jumps the queue and
23 breaks the order for the whole page, never use one. */The `opacity: 0` case is the one that catches people, because the menu is invisible and yet Tab still walks through every item in it, the user is tabbing into nothing. And the rule about positive `tabindex` is worth stating with the reason: any value above zero is placed before every element in the natural order across the entire document, so one careless `tabindex="3"` reorders the whole page.
How do you manage focus in a modal?
Three things must happen, and they are the most commonly asked accessibility implementation question. Focus moves into the dialog when it opens; it is trapped inside while it is open, so Tab cannot reach the page behind; and it returns to the element that opened it when it closes. Escape must close it, and the rest of the page should be hidden from assistive technology.
1function Modal({ onClose, children }) {
2 const ref = useRef(null);
3 const opener = useRef(null);
4
5 useEffect(() => {
6 opener.current = document.activeElement; // remember where we came from
7 ref.current?.focus(); // move focus in
8
9 const onKey = (e) => {
10 if (e.key === "Escape") onClose();
11 if (e.key !== "Tab") return;
12
13 const items = ref.current.querySelectorAll(
14 'a[href], button:not([disabled]), input, select, textarea, [tabindex]:not([tabindex="-1"])'
15 );
16 const first = items[0], last = items[items.length - 1];
17 if (e.shiftKey && document.activeElement === first) {
18 e.preventDefault(); last.focus(); // wrap backwards
19 } else if (!e.shiftKey && document.activeElement === last) {
20 e.preventDefault(); first.focus(); // wrap forwards
21 }
22 };
23
24 document.addEventListener("keydown", onKey);
25 return () => {
26 document.removeEventListener("keydown", onKey);
27 opener.current?.focus(); // → return focus on close
28 };
29 }, [onClose]);
30
31 return (
32 <div ref={ref} role="dialog" aria-modal="true" aria-labelledby="t" tabIndex={-1}>
33 <h2 id="t">Confirm purchase</h2>
34 {children}
35 </div>
36 );
37}
38
39// The native element handles the trap, Escape and the top layer for you:
40// dialog.showModal() → focus trap, Escape, ::backdrop, inert backgroundMentioning `<dialog>` with `showModal()` at the end is what turns this from a correct answer into a current one: it provides the focus trap, Escape handling and an inert background natively, and it is supported everywhere that matters now. The manual version is still worth being able to write, because most codebases contain one, and because the interviewer is checking whether you know what the native element is doing for you.
This is 1 of 200+ questions in the Complete Frontend Kit
Get every question with detailed answers, follow-ups and real code, plus coding challenges and a last-minute revision sheet. One-time payment, instant access.
⚡ Get the Complete Frontend Kit → ₹499What is ARIA and when should you use it?
ARIA is a set of attributes that change what assistive technology reports, role, name, state and relationships. The critical property, and the one candidates most often miss, is that it adds no behaviour whatsoever. `role="button"` does not make a div focusable or clickable by keyboard; it only makes a screen reader call it a button, which arguably makes things worse by promising an interaction that does not work.
1<button aria-expanded="false" aria-controls="menu">Filters</button>
2<div id="menu" hidden>…</div>
3<!-- → announced as "Filters, collapsed"; update the attribute on toggle -->
4
5<button aria-pressed="true">Bold</button>
6<!-- → a toggle button: "Bold, pressed". Different from a checkbox. -->
7
8<input aria-invalid="true" aria-describedby="err" />
9<p id="err">Enter a valid email address</p>
10<!-- → the error is read as part of the field, not as orphaned text -->
11
12<nav aria-label="Pagination">…</nav>
13<!-- → distinguishes it from the main nav landmark in a landmark list -->
14
15<div aria-hidden="true">…</div>
16<!-- → removed from the tree. NEVER put it on anything focusable: a -->
17<!-- → keyboard user can still reach a control the screen reader denies -->
18
19<!-- Redundant: the role is already implied by the element -->
20<nav role="navigation"> <button role="button"> <main role="main">The five rules of ARIA are worth being able to summarise: prefer a native element; do not change native semantics; every interactive ARIA control must be keyboard operable; never put `aria-hidden` or `tabindex="-1"` on a focusable element; and every interactive element needs an accessible name. If you can name the fourth one and explain why, a control reachable by Tab but hidden from the screen reader leaves the user focused on something that announces nothing, you are well past the level this question is usually pitched at.
How do you announce dynamic updates?
With a live region. When content changes in a part of the page the user is not focused on, a toast, a validation summary, a search result count, a cart total, a screen reader has no reason to notice. `aria-live` tells it to announce changes to that container without moving focus, which is exactly the behaviour a notification needs.
1<div role="status" aria-live="polite">3 results found</div>
2<!-- → announced after the current speech finishes. The default choice. -->
3
4<div role="alert" aria-live="assertive">Payment failed</div>
5<!-- → interrupts immediately. Reserve for genuine errors; overuse of -->
6<!-- → assertive makes an interface hostile to listen to -->
7
8<!-- The mistake: the region must exist in the DOM BEFORE it has content. -->
9<!-- Rendering the whole element only when a message appears means the -->
10<!-- screen reader never observed it changing, so nothing is announced. -->
11{message && <div role="status">{message}</div>} <!-- → often silent -->
12<div role="status">{message}</div> <!-- → announced -->
13
14<!-- aria-atomic reads the whole region rather than just the changed part -->
15<div role="status" aria-atomic="true">Total: ₹1,047</div>
16<!-- → reads "Total: ₹1,047", not just "1,047" -->The conditional-render trap is the detail that marks real experience, because it is the reason live regions "do not work" in most React codebases. The container has to be present and empty from the start so the assistive technology is already observing it; if the element itself appears at the same moment as the text, the change is never detected. Rendering an always-present region and changing only its contents is the fix.
When should alt text be empty?
When the image adds nothing that the surrounding text does not already say. `alt=""` removes the image from the accessibility tree entirely, which is correct for decoration, a background flourish, an icon next to a label that already reads "Delete", a spacer. Omitting the attribute is not the same thing: with no `alt` at all, many screen readers fall back to reading the filename.
1<img src="/hero-swirl.png" alt="" />
2<!-- → decorative: correctly silent -->
3
4<img src="/hero-swirl.png" />
5<!-- → wrong: some readers announce "hero dash swirl dot p n g" -->
6
7<button><img src="/trash.svg" alt="Delete kit" /></button>
8<!-- → the image IS the label, so it must carry one -->
9
10<button><img src="/trash.svg" alt="" /> Delete kit</button>
11<!-- → the text is already the label, so the icon is decorative -->
12
13<img src="/revenue-chart.png"
14 alt="Revenue grew from ₹2L in January to ₹9L in June" />
15<!-- → describe the INFORMATION, not the picture. "Chart showing -->
16<!-- → revenue" tells a blind user nothing they did not already know -->What are the contrast and colour requirements?
WCAG 2.1 level AA requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text, 18.66px bold or 24px regular and above. The same 3:1 minimum applies to interface components and meaningful graphics, which covers input borders, icons and focus indicators. Level AAA raises text to 7:1, which is a reasonable target for body copy but rarely a hard requirement.
The rule that gets asked alongside it is that colour must never be the only way information is conveyed. A red border on an invalid field is invisible to a user with the most common form of colour blindness and to anyone in bright sunlight, so it needs an icon, a message, or both. This applies to charts as well, pair colour with a pattern, a direct label, or distinct line shapes. Both of these are cheap to implement and are the two accessibility failures that show up most often in automated audits, which makes them safe things to raise unprompted.
How do you test accessibility?
In three layers, and the honest framing of the first one is what interviewers want to hear. Automated tools such as axe, Lighthouse and eslint-plugin-jsx-a11y catch roughly a third of issues, missing alt attributes, contrast failures, absent labels, because those are the machine-checkable ones. They cannot tell you whether the focus order makes sense or whether your alt text is useful.
The second layer is a keyboard pass: unplug the mouse, Tab through the whole page, and check that you can reach and operate everything, that you can always see where you are, and that no trap holds you. It takes two minutes and finds more real problems than any scanner. The third is a screen reader, VoiceOver on macOS and iOS, NVDA on Windows, TalkBack on Android, which is the only way to hear what your interface actually announces. Saying you have spent an hour with NVDA or VoiceOver is a genuinely differentiating claim, provided it is true.
What loses marks in an accessibility round
The failures cluster tightly, and every one of them is avoidable.
- →Removing focus outlines with :focus { outline: none } and adding nothing back.
- →Believing role="button" makes an element keyboard operable.
- →Building a modal with no focus trap and no idea where focus returns.
- →Adding ARIA to native elements that already imply the role.
- →Treating a placeholder as a label.
- →Conveying state through colour alone, a red border with no message.
- →Claiming a Lighthouse score of 100 means the page is accessible.
Frequently asked questions
- What is the accessibility tree?
- A structure the browser builds alongside the DOM containing each node's role, accessible name, state and value. Assistive technology reads that tree rather than your visual styles, which is why a div with a click handler is invisible as a control and why aria-hidden removes something from it without hiding it on screen.
- What is the biggest accessibility win?
- Using the native element. A button, link, input or details element arrives with a role, focus behaviour, keyboard handling and platform conventions already implemented across every browser and screen reader. Most accessibility bugs come from re-implementing one of those with a div and rebuilding only part of the behaviour.
- Does role="button" make a div keyboard accessible?
- No. ARIA changes only what assistive technology announces and adds no behaviour at all. The div is still not focusable without tabindex, still does not activate on Space, has no disabled state and cannot submit a form. Adding the role without the behaviour arguably makes it worse by promising an interaction that does not work.
- How do you make a modal accessible?
- Move focus into the dialog when it opens, trap Tab inside while it is open, close on Escape, and return focus to the element that opened it. Give it role="dialog", aria-modal="true" and a label. The native dialog element with showModal() provides the trap, Escape handling and an inert background for free.
- Why should I never remove the focus outline?
- It is the only indication a keyboard user has of where they are on the page. If the default ring clashes with your design, replace it rather than removing it, :focus-visible lets you show a custom ring for keyboard navigation while leaving mouse clicks unstyled, so there is no reason to set outline: none unconditionally.
- What is the difference between aria-live polite and assertive?
- polite queues the announcement until the screen reader finishes what it is currently saying; assertive interrupts immediately. Use polite for status updates, search result counts and toasts, and reserve assertive for genuine errors, overusing it makes an interface exhausting to listen to.
- Why is my aria-live region not announcing anything?
- Almost always because the element is rendered at the same moment as its content. The region has to exist and be empty in the DOM first so assistive technology is already observing it; if the container and the message appear together, the change is never detected. Render an always-present region and update only its contents.
- When should alt text be empty?
- When the image conveys nothing beyond the surrounding text, decorative flourishes, or an icon beside a label that already reads Delete. alt="" removes it from the accessibility tree, which is correct. Omitting the attribute entirely is different and worse: many screen readers then read the filename aloud.
- What contrast ratio does WCAG require?
- Level AA requires 4.5:1 for normal text and 3:1 for large text, meaning 18.66px bold or 24px regular and above. The same 3:1 minimum applies to interface components and meaningful graphics, including input borders and focus indicators. Level AAA raises body text to 7:1.
- Can I use colour alone to show an error state?
- No. Colour alone excludes users with colour vision deficiency and anyone viewing the screen in poor conditions. Pair it with an icon, a text message, or both, and apply the same rule to charts by adding patterns or direct labels rather than relying on a colour legend.
- How do you test accessibility?
- In three layers: automated tools such as axe or Lighthouse, which catch roughly a third of issues; a keyboard-only pass through the whole page, which finds more real problems in two minutes than any scanner; and a screen reader, VoiceOver, NVDA or TalkBack, to hear what the interface actually announces.
- Is a Lighthouse accessibility score of 100 enough?
- No, and saying so is a good answer. Automated checks cover what a machine can verify, missing labels, contrast, alt attributes. They cannot judge whether the tab order is logical, whether focus goes somewhere sensible, or whether your alt text is useful. A perfect score is a floor, not a result.
This is 1 of 200+ questions in the Complete Frontend Kit
Get every question with detailed answers, follow-ups and real code, plus coding challenges and a last-minute revision sheet. One-time payment, instant access.
⚡ Get the Complete Frontend Kit → ₹499