Web Accessibility 101: A Developer's Guide to WCAG & ARIA
Accessibility isn't optional—it's essential. Master the POUR principles, understand ARIA attributes like aria-label & aria-live, and learn how to build inclusive web experiences.
Introduction
The web was designed to work for everyone, regardless of their hardware, software, language, location, or ability. When the web meets this goal, it is accessible to people with a diverse range of hearing, movement, sight, and cognitive ability.
However, 96.3% of the top 1 million homepages had detectable accessibility failures in 2023 (WebAIM).
Building accessible websites isn't just about avoiding lawsuits or following rules; it's about empathy and user experience. A website that is easy for a screen reader to navigate is usually cleaner, faster, and better for SEO for everyone else.
In this guide, we'll demystify the WCAG (Web Content Accessibility Guidelines) and look at practical ways to fix common issues using HTML and ARIA.
The 4 Principles of Accessibility (POUR)
The WCAG guidelines are organized around four core principles. If your site fails one of these, it fails accessibility.
1. Perceivable
Information and user interface components must be presentable to users in ways they can perceive.
- Example: Text alternatives (Alt text) for images so blind users can "see" them via screen readers.
- Example: Captions for videos for deaf users.
2. Operable
User interface components and navigation must be operable.
- Example: Everything must be clickable via keyboard only (no mouse).
- Example: Users must have enough time to read and use content (no 3-second auto-closing modals).
3. Understandable
Information and the operation of user interface must be understandable.
- Example: Predictable navigation (menus shouldn't change order randomly).
- Example: Error messages should clearly explain what went wrong, not just turn red.
4. Robust
Content must be robust enough that it can be interpreted reliably by a wide variety of user agents, including assistive technologies.
- Example: Writing valid HTML so browsers and screen readers don't break when parsing your code.
Semantic HTML: The Foundation
The single biggest thing you can do for accessibility is usually the simplest: Use correct HTML tags.
The Button vs. Div Debate
This is the most common accessibility violation on the web.
Bad:
// This is invisible to keyboard users and screen readers
<div onClick={submitForm} className="btn">
Submit
</div>
Good:
// This gets focus, keyboard support, and announces "Button" automatically
<button onClick={submitForm} className="btn">
Submit
</button>
If you must use a div (don't), you have to manually add role="button", tabIndex="0", and keydown listeners for Enter/Space. Why do all that work when <button> does it for free?
ARIA Deep Dive: Use It Wisely
ARIA (Accessible Rich Internet Applications) is a set of attributes that allow you to modify how an element translates to the Accessibility Tree.
The First Rule of ARIA: Use native HTML elements to satisfy the requirement effectively. If you can use a native HTML element or attribute, do so.
However, typically when building complex components like Modals, Accordions, or Custom Dropdowns, we need ARIA.
1. aria-label vs aria-labelledby
These provide an accessible name for an element.
Scenario: An icon-only button (e.g., a "Trash" icon for delete).
<!-- BAD: Screen reader says "Button" (What button??) -->
<button>
<i class="fa fa-trash"></i>
</button>
<!-- GOOD: Screen reader says "Button, Delete Item" -->
<button aria-label="Delete Item">
<i class="fa fa-trash"></i>
</button>
Scenario: A modal dialog whose title is already visible on screen.
<div role="dialog" aria-labelledby="modal-title">
<h2 id="modal-title">Confirm Deletion</h2>
<p>Are you sure?</p>
</div>
Using aria-labelledby avoids duplication by pointing to an existing ID.
2. aria-expanded & aria-controls
These are essential for Accordions and Dropdowns. They tell the user "This button controls meaningful content, and it is currently open/closed."
<!-- Button controlling the accordion panel -->
<button
aria-expanded="false"
aria-controls="accordion-content-1"
onClick={toggleAccordion}
>
Show Details
</button>
<!-- The content panel itself -->
<div id="accordion-content-1" hidden>
<p>Here are the details...</p>
</div>
When the user clicks, you must toggle aria-expanded to "true". Without this, a blind user has no idea that clicking the button revealed new content.
3. aria-live for Dynamic Content
How do you tell a screen reader user that a "Toast" message just popped up in the corner? If you don't tell them, they won't know.
aria-live="polite": Waits for the user to stop typing/interaction, then announces the update. (Good for Form Errors, Toasts).aria-live="assertive": Interrupts the user immediately. (Good for Critical server errors, "Session Timeout").
<div role="status" aria-live="polite">
{message && <span>{message}</span>}
</div>
4. aria-hidden="true"
Use this to hide decorative content from screen readers.
<!-- The user hears "Button, Settings". They don't need to hear "Gear Icon" -->
<button>
<span class="icon-gear" aria-hidden="true"></span>
Settings
</button>
5. aria-describedby
While aria-label gives an element a name, aria-describedby gives it a description. This is perfect for tooltips or helper text.
<label for="password">Password</label>
<input
id="password"
type="password"
aria-describedby="password-help"
/>
<span id="password-help">Must be 8 characters long</span>
When the user focuses the input, the screen reader announces: "Password, secure edit text... Must be 8 characters long."
6. aria-current
Use this to indicate the current item within a navigation set (like pagination or a sidebar).
Note: Don't use
aria-selectedfor links!aria-selectedis for Tabs and Grids.
<nav>
<a href="/home">Home</a>
<!-- Tells the user "This is the page you are on" -->
<a href="/about" aria-current="page">About</a>
<a href="/contact">Contact</a>
</nav>
7. Form Accessibility: aria-invalid
Don't just turn the border red when a form has an error. Blind users won't see the red border.
<input
type="email"
aria-invalid="true"
aria-errormessage="email-error"
/>
<span id="email-error" class="error-text">Please enter a valid email.</span>
This ensures the error state is programmatic, not just visual.
Essential Checklist
Before you ship, check these 5 things:
- Keyboard Navigation: Can you Tab through the entire page and use every interactive element without a mouse?
- Focus Styles: Is there a visible "outline" or focus ring when you Tab to a link/button? (Never do
outline: none!) - Contrast: Is the text easy to read? (Use tools like the Chrome Inspector to check ratios).
- Alt Text: Do all images have
altattributes? (Emptyalt=""is fine for decorative images). - Headings: Do you use
h1throughh6in logical order? (Don't skip from h1 to h4 just for sizing).
Common Interview Questions
I searched the top tech interview resources, and these are the most common accessibility questions asked to Frontend Engineers.
Q1: What is a "Skip Link" and why is it important?
A "Skip Link" is a hidden link at the very top of the page (usually "Skip to Content").
- Purpose: It allows keyboard users to bypass repetitive navigation menus and jump straight to the main content.
- Behavior: It should be hidden by default but become visible when it receives focus (via the
Tabkey). Without it, keyboard users have to hitTab20+ times on every page load just to read the article.
Q2: How do you handle Focus Management in a Single Page Application (SPA)?
In SPAs (like React/Next.js), when a user navigates to a new page, the focus often remains on the last clicked link (or is lost entirely).
- The Fix: You must manually move focus to the top of the new page (usually the
<h1>or a wrapper div) after a route change. - Modals: When opening a modal, focus should get trapped inside it. When closing it, focus should return to the button that opened it.
Q3: What are the specific WCAG Contrast Ratio requirements?
- AA Standard (Minimum):
- 4.5:1 for normal text.
- 3.0:1 for large text (18pt+ or 14pt bold).
- AAA Standard (Enhanced):
- 7.0:1 for normal text.
- 4.5:1 for large text.
- UI Components: Buttons and inputs must have a 3.0:1 contrast ratio against the background.
Q4: What is the difference between aria-label, aria-labelledby, and aria-describedby?
aria-label: Use for invisible labels (e.g., a "close" icon button).aria-labelledby: Use to point to another element that acts as the label (e.g., a modal header acting as the modal's label).aria-describedby: Use for additional info, like helper text ("Password must be 8 chars").
Q5: "No ARIA is better than Bad ARIA." Explain.
Native HTML elements (buttons, inputs, links) have accessibility baked in (focus states, keyboard listeners, screen reader roles).
- Bad ARIA:
<div role="button">Click</div>(Fails content checks, no keyboard support). - Good ARIA: Use it ONLY when filling gaps in HTML (like custom dropdowns or popups).
- Rule: If HTML can do it, don't use ARIA.
Summary
Accessibility is a journey, not a destination. You won't get everything perfect on day one, but understanding Semantic HTML and properly applying ARIA where needed will put you ahead of 96% of the web.
Build for everyone. The web is better that way.
🧠 Test Your Knowledge
Now that you've learned the concepts, let's see if you can apply them! Take this quick quiz to test your understanding.