WCAG Basics — The Mouse-Only Button Problem
Over 1.3 billion people face barriers from buttons without keyboard support.
20+ years shipping production JavaScript and front-end systems at scale. Drawn from code that ran under real load.
- WCAG stands for Web Content Accessibility Guidelines, a W3C standard.
- Four principles: Perceivable, Operable, Understandable, Robust (POUR).
- Keyboard accessibility is the top failure: every interactive element must work without a mouse.
- Color contrast ratio must be at least 4.5:1 for normal text (AA).
- Real cost: inaccessibility lawsuits average $45,000 per case in the US.
- Biggest mistake: relying on ARIA instead of native semantic HTML.
Imagine a brand-new public library. It has thousands of books, but the only entrance is a steep staircase with no ramp, the signs are written in tiny grey text on white walls, and the staff whispers so quietly you can barely hear them. The building exists, but huge groups of people simply can't use it. Web accessibility is the ramp, the clear signage, and the loud speaker system — it's the set of design and code decisions that make sure your website actually works for everyone, including people who are blind, deaf, have motor limitations, or rely on assistive technology like screen readers.
Every day, roughly 1.3 billion people worldwide live with some form of disability. When a developer ships a button that only works with a mouse, or an image with no description, or a form that flashes error messages in red with no label — those people hit a wall. Accessibility isn't a niche concern or a 'nice to have'; it's the difference between a product that includes everyone and one that quietly turns away a market segment larger than the population of China. On top of the ethical argument, there's a legal one: the ADA in the US, EN 301 549 in the EU, and the EAA coming into force in 2025 all have real teeth. Companies have been sued — and lost — over inaccessible websites.
The Web Content Accessibility Guidelines (WCAG) are the internationally recognised standard that answers the question: 'How do we actually measure accessibility?' Published by the W3C, WCAG gives us a concrete checklist organised under four principles — Perceivable, Operable, Understandable, and Robust (POUR). Each principle contains testable success criteria rated A, AA, and AAA. Most legal requirements and company accessibility policies target WCAG 2.1 Level AA, which is the sweet spot between 'genuinely useful' and 'practically achievable'.
By the end of this article you'll understand the four POUR principles and what they demand from your code, how to write semantic HTML and ARIA attributes that screen readers actually understand, how to manage keyboard focus so power users and motor-impaired users can navigate your UI, how to check colour contrast programmatically, and how to audit your own pages. You'll leave with patterns you can drop into real projects today — not theory, not checklists.
Why Keyboard-Only Users Break Your Button
Web accessibility (WCAG) means building interfaces that work for everyone, including people who cannot use a mouse. The core mechanic is simple: every interactive element must be operable via keyboard alone. This is not a nice-to-have — it's a legal and engineering requirement under WCAG 2.1 Success Criterion 2.1.1. A button that only responds to click events is broken for users relying on Tab, Enter, or Space. The fix is trivial: use native <button> or <input> elements, or add role="button" with proper keyboard event handlers. In practice, the most common violation is a <div> styled as a button with only an onClick listener. That div is invisible to keyboard navigation and screen readers. The result: a user who cannot use a mouse is locked out of your primary action — submit, buy, delete. This matters because 15-20% of users have a motor or visual impairment that affects mouse use. In enterprise systems, a single inaccessible button can block an entire workflow, triggering compliance audits and legal exposure. Always test every interactive element with Tab and Enter before shipping.
The Four Principles of WCAG (POUR)
WCAG 2.1 is built on four principles. Every success criterion falls under one of them.
- Perceivable: Information and UI components must be presentable to users in ways they can perceive. This means providing text alternatives for non-text content, captions for multimedia, and adaptable layouts.
- Operable: UI components and navigation must be operable. All functionality must be available from a keyboard, users must have enough time to read and use content, and content must not cause seizures.
- Understandable: Information and the operation of the user interface must be understandable. Text must be readable, web pages must appear and operate in predictable ways, and input errors must be identifiable and describable.
- Robust: Content must be robust enough to be interpreted reliably by a wide variety of user agents, including assistive technologies. Use valid HTML and ARIA appropriately.
Semantic HTML: The Backbone of Accessibility
Before reaching for ARIA, use native HTML elements. They have built-in roles, states, and keyboard handlers.
- Use
<nav>,<main>,<header>,<footer>,<section>,<article>,<aside>for landmarks — screen reader users navigate by them. - Use
<button>for actions, not<div>or<a>unless going to a URL. - Use
<label>for every form input. Placeholder is not a label. - Use
<h1>through<h6>in hierarchical order. Don't skip levels. - Use
<table>with<caption>,<th scope>, and<thead>/<tbody>for data tables.
ARIA: When HTML Isn't Enough
ARIA (Accessible Rich Internet Applications) supplements HTML for complex widgets like tabs, sliders, and modals.
- Roles:
role="tab",role="tabpanel",role="dialog",role="alert". - States and Properties:
aria-expanded,aria-label,aria-labelledby,aria-describedby,aria-hidden,aria-live. - First rule of ARIA: Don't use ARIA if you can use a native HTML element that provides the semantics and behavior built-in.
- Native HTML: free accessibility, no ARIA needed.
- When you build a custom widget, you must manually add role, name, and keyboard support.
- ARIA labels override visible text — be careful not to conflict.
- Use aria-live regions sparingly — they disrupt screen readers.
Keyboard Navigation and Focus Management
Keyboard accessibility is a top-priority WCAG criterion (2.1.1). Every interactive element must be reachable and operable via keyboard.
- Tab order: Elements receive focus in DOM order unless
tabindexis set. Positive tabindex values create a confusing order — avoid them. Usetabindex="0"to make a non-focusable element focusable, andtabindex="-1"for programmatic focus only. - Focus indicators: Never remove
:focusoutline without replacing it with a visible custom style. WCAG requires a 3:1 contrast ratio for the focus indicator. - Skip links: Provide a 'Skip to content' link as the first focusable element on the page.
Color Contrast and Non-Text Contrast
WCAG 2.1 Level AA requires a minimum contrast ratio of 4.5:1 for normal text and 3:1 for large text (18pt+ or 14pt bold).
- Use tools like WebAIM Contrast Checker or axe DevTools to measure ratios.
- Contrast applies to text against its background, not just to the text itself.
- Non-text contrast: UI components and graphical objects must have 3:1 contrast against adjacent colors. This includes borders of input fields, icons, and charts.
contrast-ratio npm package to automate checks.Testing and Auditing for WCAG Compliance
Automated tools catch ~30% of issues. The rest require manual testing.
- Automated tools: aXe, WAVE, Lighthouse accessibility audit, pa11y. Integrate aXe into CI to catch regressions.
- Manual checks:
- 1. Keyboard-only navigation: Tab through all interactive elements.
- 2. Screen reader testing: Use VoiceOver (macOS) or NVDA (Windows) with the browser.
- 3. Zoom to 200%: Check that content doesn't overlap or get cut off.
- 4. Disable images: Ensure all information is still available via alt text.
- Checklist: WCAG-EM (Website Accessibility Conformance Evaluation Methodology) provides a structured approach.
Why ARIA Labels Can Actually Break Navigation
The truth is, ARIA doesn't fix broken HTML. It enhances it. Add aria-label to a native <button> and you're duplicating effort. Slap it on a <div> pretending to be a button, and you've built a trap. Screen readers will announce your label, but keyboard users still can't focus or activate it without JavaScript. That's a WCAG Failure. I've seen production incidents where teams added role="button" and aria-label to a <span>, thinking they'd made it accessible. They hadn't. The element wasn't focusable, didn't respond to Enter or Space, and broke keyboard navigation entirely. The rule is simple: ARIA doesn't change behavior, only semantics. Always start with semantic HTML. Use ARIA only when the native element can't express the interface you need. Audit your ARIA with a screen reader, not just the accessibility tree.
role="button" on a <div> or <span> unless you also add tabindex="0" and a full keyboard event handler. Test with a real screen reader, not just Chrome DevTools.The Hidden Cost of Dynamic Content: Announcements Nobody Hears
Your single-page app fetches new results. The list updates. Sighted users see it. Screen reader users? They're left guessing. WCAG 4.1.3 (Status Messages) demands that changes like '5 results found' or 'Loading...' be announced without moving focus. The fix is role="status" or aria-live. I've debugged a production incident where a shopping cart update caused users to think nothing happened. Turned out the message was a plain <div> with no live region. Users attempted to add the same item three times. Cost: abandoned carts and angry calls. The pattern is simple: wrap dynamic status messages in a container with aria-live="polite" (for non-critical updates) or aria-live="assertive" (for urgent alerts like errors). Use role="status" for polite announcements and role="alert" for urgent ones. Never use aria-live on the entire page — only the element that changes. And always test by turning off your monitor and using only the screen reader.
aria-live="assertive" for search results or status updates — it interrupts the user's current task. Use polite unless it's a critical error (e.g., 'Session expiring').aria-live="polite" or role="status" so screen readers announce changes without moving focus.The Unclickable Checkout Button
- Use native HTML buttons for click actions — they're free with keyboard support.
- If you must use a non-semantic element, add role='button', tabindex='0', and a keydown handler for Enter and Space.
- Always test with keyboard only before shipping any interactive control.
Key takeaways
Common mistakes to avoid
4 patternsMemorising syntax before understanding the concept
Skipping practice and only reading theory
Using role='button' on a <div> without adding keyboard support
Removing outline on focus without providing an alternative
Interview Questions on This Topic
What are the four principles of WCAG? Explain each.
Frequently Asked Questions
20+ years shipping production JavaScript and front-end systems at scale. Drawn from code that ran under real load.
That's HTML & CSS. Mark it forged?
6 min read · try the examples if you haven't