Senior Frontend Interview Guide, Part 10: Accessibility and WCAG Compliance
Accessibility (often abbreviated a11y) means people with disabilities can perceive, understand, navigate, and interact with your application. For banks and financial institutions it is also a legal obligation: in the United States, the Americans with Disabilities Act has been applied to websites in…

This article is Part 10 of 18 in The Senior Frontend Engineer Interview Guide, and includes 22 interview questions with model answers.
Accessibility (often abbreviated a11y) means people with disabilities can perceive, understand, navigate, and interact with your application. For banks and financial institutions it is also a legal obligation: in the United States, the Americans with Disabilities Act has been applied to websites in many lawsuits and settlements, and in the European Union the European Accessibility Act applies to banking services from June 2025. Interviewers for enterprise roles expect you to know WCAG, write accessible components, and test them. This chapter gives you the knowledge to do that.
Who Benefits
About one in four adults in the United States reports some form of disability. Accessibility serves people who are:
- Blind or low vision: screen readers (JAWS, NVDA, VoiceOver, TalkBack), screen magnifiers, high contrast modes, zoom.
- Deaf or hard of hearing: captions and transcripts.
- Motor impaired: keyboard-only use, switch devices, voice control (Dragon, Voice Control), larger targets.
- Cognitively impaired: clear language, consistent navigation, enough time, error prevention.
- Situationally limited: bright sunlight, a broken trackpad, holding a baby in one arm.
Accessible design is good design for everyone: captions help in noisy places, keyboard shortcuts help power users, clear error messages help all users.
WCAG Overview
The Web Content Accessibility Guidelines are published by the W3C. WCAG 2.1 (2018) is the most commonly referenced standard in regulations; WCAG 2.2 (October 2023) is the current version and adds nine success criteria (and removes 4.1.1 Parsing). WCAG 3.0 is in development and uses a different model.
The Four Principles: POUR
- Perceivable: information must be presentable in ways users can perceive (text alternatives, captions, adaptable content, sufficient contrast).
- Operable: interface components must be operable (keyboard access, enough time, no seizure-inducing content, navigable, input modalities).
- Understandable: information and operation must be understandable (readable, predictable, input assistance).
- Robust: content must work with current and future user agents, including assistive technologies (valid names, roles, values).
Conformance Levels
Each success criterion has a level: A (minimum), AA (the standard target for most legal requirements and enterprise policies), and AAA (enhanced, not usually required for entire sites). “WCAG 2.1 AA compliant” means meeting all A and AA criteria.
Success Criteria Every Frontend Engineer Should Know
| Criterion | Level | Summary |
|---|---|---|
| 1.1.1 Non-text Content | A | Images and icons have text alternatives; decorative images are hidden |
| 1.3.1 Info and Relationships | A | Structure (headings, lists, tables, labels) is conveyed programmatically |
| 1.3.5 Identify Input Purpose | AA | Use autocomplete on fields collecting user information |
| 1.4.1 Use of Color | A | Color is not the only means of conveying information |
| 1.4.3 Contrast (Minimum) | AA | Text contrast at least 4.5:1, or 3:1 for large text |
| 1.4.4 Resize Text | AA | Text resizes to 200% without loss of content |
| 1.4.10 Reflow | AA | No horizontal scrolling at 320 CSS px width (400% zoom), except for data tables and similar |
| 1.4.11 Non-text Contrast | AA | UI components and graphical objects have at least 3:1 contrast |
| 1.4.12 Text Spacing | AA | Content works when users increase letter, word, line, and paragraph spacing |
| 1.4.13 Content on Hover or Focus | AA | Tooltips are dismissible, hoverable, and persistent |
| 2.1.1 Keyboard | A | All functionality is available from the keyboard |
| 2.1.2 No Keyboard Trap | A | Focus can always move away from a component |
| 2.2.1 Timing Adjustable | A | Users can extend time limits (session timeouts) |
| 2.4.1 Bypass Blocks | A | Skip links or landmarks to skip repeated content |
| 2.4.3 Focus Order | A | Focus order preserves meaning |
| 2.4.6 Headings and Labels | AA | Headings and labels describe purpose |
| 2.4.7 Focus Visible | AA | Keyboard focus is visible |
| 2.4.11 Focus Not Obscured (Minimum) | AA (2.2) | Focused element is not entirely hidden by sticky headers or banners |
| 2.5.3 Label in Name | A | The accessible name contains the visible label text |
| 2.5.7 Dragging Movements | AA (2.2) | Dragging actions have a single-pointer alternative |
| 2.5.8 Target Size (Minimum) | AA (2.2) | Targets are at least 24 by 24 CSS px, or adequately spaced |
| 3.1.1 Language of Page | A | The page language is set (lang) |
| 3.2.2 On Input | A | Changing a setting does not unexpectedly change context |
| 3.3.1 Error Identification | A | Errors are identified and described in text |
| 3.3.2 Labels or Instructions | A | Inputs have labels or instructions |
| 3.3.3 Error Suggestion | AA | Suggestions for fixing errors are provided |
| 3.3.4 Error Prevention (Legal, Financial, Data) | AA | Financial transactions are reversible, checked, or confirmed |
| 3.3.7 Redundant Entry | A (2.2) | Do not ask users to re-enter information already provided in the same process |
| 3.3.8 Accessible Authentication (Minimum) | AA (2.2) | No cognitive function test (like memorizing or transcribing) required to log in, unless alternatives exist; allow paste and password managers |
| 4.1.2 Name, Role, Value | A | Custom components expose correct name, role, state |
| 4.1.3 Status Messages | AA | Status messages are announced without moving focus |
3.3.4 and 3.3.8 are particularly relevant to banking: financial submissions need a review step or the ability to cancel, and login must support password managers and paste (blocking paste in password or OTP fields fails WCAG 2.2).
The Accessibility Tree
Browsers build an accessibility tree from the DOM, exposing each relevant element’s role (button, link, textbox), name (its label), state (expanded, checked, disabled), and value to assistive technologies through platform APIs. You can inspect it in Chrome DevTools (Elements panel, Accessibility tab) and Firefox’s Accessibility Inspector.
Accessible Name Computation
The accessible name is computed in roughly this priority:
aria-labelledby(references other elements’ text)aria-label- Native labeling:
<label for>,alt,<caption>,<legend>,title(as a last resort) - Text content (for roles that allow naming from content, such as buttons and links)
<button aria-label="Close dialog"><svg aria-hidden="true">...</svg></button>
<button><svg aria-hidden="true">...</svg><span class="visually-hidden">Download statement</span></button>
ARIA
Accessible Rich Internet Applications (WAI-ARIA) is a set of attributes that add or change roles, states, and properties in the accessibility tree. ARIA changes only semantics; it adds no behavior. A div with role="button" is announced as a button but is not focusable and does not respond to Enter or Space unless you add that yourself.
The Rules of ARIA
- If a native HTML element provides the semantics and behavior you need, use it instead of ARIA.
- Do not change native semantics unless you really have to (
<h2 role="button">is wrong). - All interactive ARIA controls must be keyboard operable.
- Do not use
role="presentation"oraria-hidden="true"on focusable elements. - All interactive elements must have an accessible name.
“No ARIA is better than bad ARIA”: surveys of the top million home pages repeatedly find that pages using ARIA have more detected accessibility errors on average than pages without it.
Commonly Used ARIA Attributes
| Attribute | Use |
|---|---|
aria-label, aria-labelledby |
Provide a name |
aria-describedby |
Associate hints and error messages |
aria-expanded |
Disclosure state of a button controlling a menu or accordion |
aria-controls |
Reference the element a control affects |
aria-current="page" |
Current item in navigation or breadcrumbs |
aria-invalid |
Field has an error |
aria-required |
Required field (native required also works) |
aria-live, role="status", role="alert" |
Announce dynamic changes |
aria-hidden="true" |
Hide decorative content from assistive technology |
aria-busy |
Region is updating |
aria-selected, aria-checked, aria-pressed |
Selection and toggle states |
aria-modal="true" |
Indicates a modal dialog |
Keyboard Accessibility
Every interactive element must be reachable and operable with the keyboard:
- Tab / Shift+Tab move between focusable elements.
- Enter activates links and buttons; Space activates buttons, checkboxes.
- Arrow keys move within composite widgets (tabs, menus, radio groups, listboxes, grids).
- Escape closes dialogs, menus, and popovers.
Focus Management Rules
- Use native focusable elements; avoid positive
tabindexvalues, which break natural order. tabindex="0"makes a custom element focusable in document order;tabindex="-1"makes it focusable programmatically only.- Never remove focus outlines without providing a visible alternative. Use
:focus-visibleto show focus rings for keyboard users. - When content changes significantly (route change, dialog open, item deleted), move focus deliberately.
Roving Tabindex
Composite widgets like tab lists and toolbars should be a single tab stop, with arrow keys moving between items. Only the active item has tabindex="0"; the others have -1.
function TabList({ tabs, selected, onSelect }) {
const refs = useRef([]);
function onKeyDown(e, index) {
const last = tabs.length - 1;
const next = { ArrowRight: index === last ? 0 : index + 1,
ArrowLeft: index === 0 ? last : index - 1,
Home: 0, End: last }[e.key];
if (next === undefined) return;
e.preventDefault();
refs.current[next].focus();
onSelect(tabs[next].id); // automatic activation
}
return (
<div role="tablist" aria-label="Account sections">
{tabs.map((t, i) => (
<button key={t.id} ref={el => (refs.current[i] = el)} role="tab"
aria-selected={selected === t.id} tabIndex={selected === t.id ? 0 : -1}
aria-controls={`panel-${t.id}`} id={`tab-${t.id}`}
onClick={() => onSelect(t.id)} onKeyDown={e => onKeyDown(e, i)}>
{t.label}
</button>
))}
</div>
);
}
The ARIA Authoring Practices Guide (APG) documents expected keyboard interactions for each widget pattern; refer to it rather than inventing behaviors.
Accessible Modal Dialogs
Modals are among the most commonly broken components. Requirements:
role="dialog"(oralertdialogfor urgent confirmations) witharia-modal="true"and an accessible name viaaria-labelledby.- Focus moves into the dialog when it opens, typically to the first focusable element or the heading.
- Focus is trapped inside while open; content behind is inert.
- Escape closes it.
- Focus returns to the element that opened it when it closes.
The native <dialog> element with showModal() provides most of this: top-layer rendering, inert background, and Escape handling.
function ConfirmDialog({ open, onClose, title, children }) {
const ref = useRef(null);
useEffect(() => {
const dialog = ref.current;
if (open && !dialog.open) dialog.showModal();
if (!open && dialog.open) dialog.close();
}, [open]);
return (
<dialog ref={ref} aria-labelledby="confirm-title" onClose={onClose}>
<h2 id="confirm-title">{title}</h2>
{children}
<button onClick={onClose}>Cancel</button>
</dialog>
);
}
For custom implementations, the inert attribute on the rest of the page (now widely supported) replaces older focus-trap hacks.
Forms and Errors
Forms are where financial apps succeed or fail at accessibility.
- Every input has a visible
<label>; placeholders are not labels. - Group related controls with
<fieldset>and<legend>(for example, “Transfer frequency” radio buttons). - Indicate required fields in text, not just with color or an asterisk without explanation.
- Associate hints and errors with
aria-describedby; setaria-invalid="true"on invalid fields. - On submit with errors, show an error summary at the top that receives focus and links to each invalid field, and/or move focus to the first invalid field.
- Error messages should say what is wrong and how to fix it: “Enter an amount between $1.00 and $10,000.00”, not “Invalid input”.
- Do not validate aggressively on every keystroke; validate on blur or submit to avoid announcing errors while the user is still typing.
- For financial transactions, provide a review step before submission (WCAG 3.3.4).
{errors.length > 0 && (
<div role="alert" tabIndex={-1} ref={summaryRef} className="error-summary">
<h2>There is a problem</h2>
<ul>
{errors.map(e => <li key={e.field}><a href={`#${e.field}`}>{e.message}</a></li>)}
</ul>
</div>
)}
Live Regions and Dynamic Content
Single-page applications change content without page loads, so screen reader users may not know something happened. Live regions announce changes:
role="status"(implicitaria-live="polite"): announced when the user is idle. Use for “3 results found”, “Saved”, “Transfer scheduled”.role="alert"(implicitaria-live="assertive"): interrupts. Use sparingly, for urgent errors.
The live region element must exist in the DOM before its content changes; injecting a new element that already contains text is often not announced reliably.
function Announcer() {
const message = useAnnouncerStore(s => s.message);
return <div role="status" className="visually-hidden">{message}</div>;
}
Route Changes in SPAs
When a client-side route changes, screen readers do not get the automatic “new page loaded” announcement. Fix by updating document.title, moving focus to the main heading (h1 with tabIndex={-1}) or the main region, and/or announcing the new page title in a live region.
Color, Contrast, and Visual Design
- Normal text needs a contrast ratio of at least 4.5:1 against its background; large text (24px regular or 18.66px bold) needs 3:1.
- UI component boundaries (input borders), focus indicators, and meaningful icons need 3:1.
- Do not rely on color alone: a negative balance shown in red should also have a minus sign or the word “overdrawn”; chart lines need patterns or labels in addition to color.
- Support zoom to 200% and reflow at 320 CSS pixels: no fixed heights that clip text, no horizontal scrolling for main content.
- Respect
prefers-reduced-motionby disabling non-essential animations. - Support Windows High Contrast (forced colors mode): avoid conveying state only through background colors and box shadows; use borders and
forced-color-adjustcarefully.
Accessible Data Tables and Charts
Financial apps are full of tables and charts.
- Use real
<table>markup with<caption>,<th scope="col">, and<th scope="row">where rows have headers. - Sortable columns use buttons in headers and
aria-sort="ascending"on the active header. - For charts, provide a text summary (“Spending increased 12% from February to March, mainly in Dining”) and the underlying data as an accessible table or download. SVG charts should have
role="img"and an accessible name, or be built with accessible charting libraries. - Amounts should be read correctly: render “-$45.00” with a real minus sign or “debit” text, and consider
aria-labeloverrides only when the visual format is ambiguous.
Testing Accessibility
No single method catches everything. Automated tools find roughly 30 to 50 percent of issues; the rest require manual testing.
Automated
- axe-core via
jest-axein unit tests,@axe-core/playwrightin end-to-end tests, and browser extensions. - eslint-plugin-jsx-a11y for static checks in JSX.
- Lighthouse accessibility audits.
- Storybook a11y addon for every component story.
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
test('transfer form has no detectable a11y violations', async () => {
const { container } = render(<TransferForm />);
expect(await axe(container)).toHaveNoViolations();
});
import AxeBuilder from '@axe-core/playwright';
test('dashboard is accessible', async ({ page }) => {
await page.goto('/dashboard');
const results = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa']).analyze();
expect(results.violations).toEqual([]);
});
Manual
- Keyboard-only pass: unplug the mouse; can you complete every task? Is focus always visible and logical?
- Screen reader testing: NVDA with Firefox or Chrome and JAWS with Chrome on Windows, VoiceOver with Safari on macOS and iOS, TalkBack with Chrome on Android.
- Zoom to 200% and 400%, and test with increased text spacing.
- Color contrast checks with tools, and a grayscale view.
- Usability testing with people with disabilities, which finds problems no checklist will.
Process
Accessibility is cheapest when built in: include acceptance criteria in user stories, review designs for contrast and focus states, use an accessible design system, add automated checks to CI so regressions fail builds, perform manual audits for major releases, and publish an accessibility statement with a feedback channel. Many enterprises document conformance in a VPAT (Voluntary Product Accessibility Template) or ACR (Accessibility Conformance Report).
Interview Questions and Answers
Q1. What is WCAG, and what level do most organizations target?
The Web Content Accessibility Guidelines from the W3C, organized under the principles Perceivable, Operable, Understandable, and Robust, with success criteria at levels A, AA, and AAA. Most organizations and regulations target WCAG 2.1 or 2.2 Level AA.
Q2. What changed in WCAG 2.2?
It added criteria including Focus Not Obscured (2.4.11), Focus Appearance (2.4.13, AAA), Dragging Movements (2.5.7), Target Size Minimum of 24 by 24 CSS pixels (2.5.8), Consistent Help (3.2.6), Redundant Entry (3.3.7), and Accessible Authentication (3.3.8 and 3.3.9), and removed 4.1.1 Parsing. Accessible Authentication matters for login and MFA: allow paste and password managers and avoid cognitive tests.
Q3. What is the first rule of ARIA?
Do not use ARIA if a native HTML element or attribute already provides the semantics and behavior. Native elements come with keyboard support and correct accessibility tree mapping for free; ARIA only changes semantics and requires you to implement behavior.
Q4. How do you make a custom button accessible?
Use a native <button>. If a custom element is unavoidable, add role="button", tabindex="0", handlers for Enter and Space, an accessible name, appropriate states like aria-pressed or aria-expanded, a visible focus style, and disabled semantics with aria-disabled. The effort shows why native elements are better.
Q5. How do you build an accessible modal?
Use role="dialog" with aria-modal and an accessible name, move focus into it on open, keep focus inside while open (or make the background inert), close on Escape, and return focus to the trigger on close. The native <dialog> element with showModal() handles most of this.
Q6. What are live regions?
Elements whose content changes are announced by screen readers without moving focus. role="status" or aria-live="polite" announces when the user is idle; role="alert" or aria-live="assertive" interrupts. The region must exist in the DOM before content is updated. Use them for search result counts, save confirmations, and errors.
Q7. How do you handle focus on route changes in a React SPA?
Update the document title, move focus to the new page’s main heading or main region (made programmatically focusable with tabIndex={-1}), and optionally announce the page title in a live region. Preserve focus for in-page updates like filtering that do not constitute a new page.
Q8. How do you make form errors accessible?
Visible text errors associated with fields via aria-describedby, aria-invalid on invalid fields, an error summary that receives focus on submit with links to each field, messages that explain how to fix the problem, and not relying on color alone.
Q9. What contrast ratios are required for AA?
4.5:1 for normal text, 3:1 for large text (at least 24px regular or about 18.66px bold), and 3:1 for UI component boundaries, focus indicators, and meaningful graphics.
Q10. What is roving tabindex?
A technique for composite widgets where only one item has tabindex="0" and the rest have -1. Tab moves into and out of the widget as one stop, and arrow keys move focus between items, updating which one has tabindex="0". Used for tabs, toolbars, menus, and radio-like groups.
Q11. What percentage of issues do automated tools catch, and how do you test the rest?
Roughly 30 to 50 percent. The rest require manual keyboard testing, screen reader testing across common browser and reader pairs, zoom and reflow checks, cognitive and content review, and usability testing with people with disabilities.
Q12. How do you make a chart accessible?
Provide a text summary of key insights, the underlying data in an accessible table or downloadable format, a meaningful accessible name for the chart, non-color differentiators (patterns, labels, markers), sufficient contrast, keyboard-accessible tooltips or data points if interactive, and respect reduced motion for animations.
Q13. What is the difference between aria-label, aria-labelledby, and aria-describedby?
aria-label provides a name as a string. aria-labelledby provides a name by referencing other elements’ text and takes precedence. aria-describedby provides supplementary description, such as a hint or error, announced after the name and role.
Q14. When would you use aria-hidden="true"?
To hide purely decorative or redundant content from assistive technologies, such as icons next to text labels or decorative images. Never on focusable elements or content that conveys unique information.
Q15. How do you handle session timeout warnings accessibly?
Warn users before the timeout with an accessible dialog that receives focus and is announced, give at least 20 seconds to respond, offer to extend the session with a simple action, and allow extending at least ten times as required by WCAG 2.2.1, unless the timeout is essential for security, in which case explain it and preserve entered data where possible.
Q16. What does WCAG 3.3.4 require for financial transactions?
For submissions that cause legal commitments or financial transactions, at least one of: the submission is reversible, data is checked for errors with an opportunity to correct, or a review and confirmation step is provided before final submission.
Q17. How do you ensure accessibility in a design system?
Use semantic elements and APG patterns, bake in keyboard behavior, labels, and focus styles, test components with axe, keyboard, and screen readers, document accessibility usage requirements, design tokens that meet contrast in every theme, and make accessible usage the easiest path for consumers (for example, required label props).
Q18. How should icons be handled?
Decorative icons next to visible text get aria-hidden="true". Icon-only buttons need an accessible name via visually hidden text or aria-label. Informative standalone icons need role="img" and a name. Ensure 3:1 contrast and adequate target size.
Q19. What is reflow, and how do you meet it?
WCAG 1.4.10 requires content to be usable at 320 CSS pixels wide (equivalent to 400% zoom on a 1280 px screen) without horizontal scrolling, except for content that needs two dimensions such as data tables and maps. Responsive layouts, relative units, and avoiding fixed widths meet it.
Q20. How do you announce the result of an asynchronous action like “Transfer scheduled”?
Update a persistent role="status" live region with the message, show a visible confirmation, and, if the user is taken to a confirmation page, move focus to its heading. Use role="alert" only for errors that need immediate attention.
Q21. What is the accessibility tree?
The structure browsers derive from the DOM and CSS to expose role, name, state, and value of elements to assistive technologies through platform accessibility APIs. Developers can inspect it in browser DevTools to verify what screen readers will receive.
Q22. Why is blocking paste in password or OTP fields a problem?
It prevents password managers and copy-paste of codes, forcing users to memorize or transcribe information, which fails WCAG 2.2 Accessible Authentication and reduces security by encouraging weak passwords.
Key Takeaways
- Target WCAG 2.2 AA; know the criteria that affect forms, focus, contrast, timing, and authentication.
- Use native HTML first; ARIA adds semantics but not behavior.
- Manage focus deliberately for dialogs, route changes, and dynamic content; announce changes with live regions.
- Combine automated testing (axe, lint) with keyboard, screen reader, and zoom testing.
- Build accessibility into the design system and the delivery process.
← Part 9: Authentication, MFA, and Frontend Security · All chapters · Part 11: Internationalization and Localization →







