How Do You Make Custom Components Screen-Reader Compatible?
Learn how to make custom UI components screen-reader compatible with correct ARIA roles, state, and keyboard support.
Expected Interview Answer
Making a custom component screen-reader compatible means giving it the correct semantic role, an accessible name, and any state it exposes (expanded, selected, checked) via ARIA attributes that update live, so assistive technology can announce and interact with it exactly as it would a native HTML element performing the same job.
Screen readers build their experience from the accessibility tree, which the browser derives from semantic HTML elements and ARIA attributes, not from visual appearance. A div styled to look like a button conveys nothing to that tree by default — no role, no name, no keyboard interaction — so a screen reader user has no idea it exists. Fixing this means following the WAI-ARIA Authoring Practices pattern for the widget being built: a custom dropdown gets role="listbox" with role="option" children and aria-selected, a custom toggle gets role="switch" with aria-checked kept in sync with state, and a custom accordion header gets aria-expanded toggled on open/close. Equally important is that ARIA only changes what is announced — it does not add keyboard behavior, so Enter/Space activation, Escape to close, and arrow-key navigation between related items must all be wired up manually to match the native element’s behavior. The single strongest rule of thumb is: prefer a real HTML element (button, select, details) whenever one already does the job, and only reach for custom ARIA roles when the native element genuinely cannot achieve the required visual or interaction design.
- Ensures assistive technology announces custom widgets with an accurate role and name
- Keeps dynamic state (expanded, selected, checked) synchronized for screen reader users
- Restores keyboard interaction parity with the native element being reimplemented
- Reduces reliance on brittle ARIA by defaulting to real HTML elements first
AI Mentor Explanation
Making a custom component screen-reader compatible is like relabeling a groundsman’s makeshift bench as an official umpire’s chair so the match officials treat it correctly. Painting the bench to look like a chair does nothing for the officials’ rulebook; only formally designating its role, plus updating a sign whenever it is occupied or vacant, makes the system recognize it properly. Likewise, a div styled as a button needs an actual role and live state, not just visual mimicry, before assistive technology treats it as a real control. That deliberate role-and-state wiring is exactly what ARIA provides.
Step-by-Step Explanation
Step 1
Prefer native HTML first
Check whether button, select, details, or another native element already does the job before building custom.
Step 2
Assign the correct ARIA role
Follow the WAI-ARIA Authoring Practices pattern for the widget (listbox, switch, tablist, etc.).
Step 3
Wire up state attributes
Keep aria-expanded, aria-selected, aria-checked, or aria-valuenow synchronized with actual component state.
Step 4
Implement matching keyboard behavior
Add Enter/Space activation, Escape, and arrow-key navigation to match the native element ARIA implies.
What Interviewer Expects
- Understanding that visual mimicry conveys nothing to the accessibility tree
- Ability to name specific ARIA roles/attributes for at least one custom widget pattern
- Awareness that ARIA does not add keyboard behavior automatically
- The “use native HTML first” principle as the default recommendation
Common Mistakes
- Styling a div to look like a button without adding role="button" and keyboard handlers
- Setting an ARIA role once but never updating its state attributes on interaction
- Assuming adding ARIA attributes alone restores keyboard behavior
- Reaching for custom ARIA widgets when a native element would have worked
Best Answer (HR Friendly)
“When I build a custom component like a dropdown or toggle, I make sure it has the right ARIA role and that its state — expanded, selected, checked — updates as the user interacts with it, and I also add the same keyboard behavior a native element would have, like arrow keys or Escape. Whenever possible, I just use the native HTML element instead, since that gets accessibility right for free.”
Code Example
function ToggleSwitch({ checked, onChange, label }: {
checked: boolean
onChange: (next: boolean) => void
label: string
}) {
return (
<button
role="switch"
aria-checked={checked}
aria-label={label}
onClick={() => onChange(!checked)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
onChange(!checked)
}
}}
>
<span className={checked ? 'thumb-on' : 'thumb-off'} />
</button>
)
}Follow-up Questions
- What ARIA roles and attributes does a custom tab list need?
- Why is using a real <button> generally preferred over a div with role="button"?
- How do you test a custom widget with a real screen reader, step by step?
- What is the difference between aria-hidden and the hidden attribute?
MCQ Practice
1. What does styling a div to look like a button give assistive technology by default?
Visual styling has no effect on the accessibility tree; role, name, and interaction must be added explicitly.
2. Does adding ARIA attributes automatically add keyboard interaction?
ARIA changes the accessibility tree/announcements only — developers must still implement matching keyboard behavior.
3. What is the recommended default when building an interactive widget?
Native elements get correct semantics, keyboard support, and focus handling for free; custom ARIA is a fallback.
Flash Cards
What does visual styling alone give a screen reader? — Nothing — the accessibility tree is unaffected by CSS appearance.
Does ARIA add keyboard behavior? — No — ARIA only affects announcements; keyboard handlers must be added manually.
Default recommendation for interactive widgets? — Use a native HTML element first; reach for custom ARIA only when necessary.
Example role/state pair for a toggle? — role="switch" with aria-checked kept in sync with the actual state.