Senior 6 min · March 06, 2026

WCAG Basics — The Mouse-Only Button Problem

Over 1.3 billion people face barriers from buttons without keyboard support.

N
Naren Founder & Principal Engineer

20+ years shipping production JavaScript and front-end systems at scale. Drawn from code that ran under real load.

Follow
Production
production tested
May 24, 2026
last updated
1,554
articles · all by Naren
 ● Production Incident
Quick Answer
  • 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.
✦ Definition~90s read
What is Web Accessibility?

WCAG (Web Content Accessibility Guidelines) is the international standard for making web content usable by people with disabilities. Developed by the W3C, WCAG 2.1/2.2 defines success criteria organized around four principles: Perceivable, Operable, Understandable, and Robust (POUR).

Imagine a brand-new public library.

The 'mouse-only button problem' is a classic failure of the Operable principle — a button that works with a mouse click but is unreachable or unusable via keyboard, screen reader, or voice control. This affects an estimated 15-20% of users who rely on keyboard navigation, including those with motor disabilities, repetitive strain injuries, or who simply prefer keyboard shortcuts.

The fix isn't about adding more JavaScript; it's about using semantic HTML (<button> instead of <div> with onclick), which gives you keyboard focusability, Enter/Space activation, and screen reader announcements for free. When HTML semantics aren't enough — say, for a custom widget like a tree or tab panel — ARIA (Accessible Rich Internet Applications) attributes like role, aria-label, and aria-expanded fill the gaps.

But ARIA is a supplement, not a substitute: misuse (like adding role="button" to a <div> without keyboard handling) creates more problems than it solves. The real-world impact is measurable: inaccessible buttons cause 70% of accessibility lawsuits in the US under ADA Title III, and fixing them post-launch costs 10-100x more than building them right.

WCAG compliance isn't a checkbox exercise — it's about understanding that your button's click handler is only half the story.

Plain-English First

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.

Native Elements Are Not Optional
A <div> with onClick and role="button" still lacks built-in keyboard handling — you must manually add onKeyDown for Enter and Space.
Production Insight
A SaaS dashboard for hospital staff used <div> buttons for 'Approve Order'. A nurse using keyboard navigation could not activate the button. The error: no onKeyDown handler. Rule: every clickable element must be focusable and activatable via keyboard — test with Tab and Enter before every deploy.
Key Takeaway
Keyboard operability is not optional — it's WCAG 2.1.1.
A <div> with onClick is not a button — use native elements or add keyboard handlers.
Test every interactive element with Tab, Enter, and Space before shipping.
WCAG Mouse-Only Button Problem Flow THECODEFORGE.IO WCAG Mouse-Only Button Problem Flow From semantic HTML to accessible keyboard navigation Mouse-Only Button Click event only; no keyboard support POUR Principles Perceivable, Operable, Understandable, Robust Semantic HTML Use ARIA as Last Resort role="button" + tabindex + key handlers Keyboard Navigation Tab order, focus indicators, Enter/Space ⚠ ARIA labels can break navigation Use native HTML first; ARIA only when no semantic element exists THECODEFORGE.IO
thecodeforge.io
WCAG Mouse-Only Button Problem Flow
Web Accessibility Wcag Basics

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.
Production Insight
Teams often treat Perceivable as 'add alt text' and forget the other three.
The Operable principle is where most lawsuits land — specifically keyboard accessibility.
Rule: audit your app with keyboard-only navigation before running automated tools.
Key Takeaway
POUR is not a checklist — it's a design philosophy.
If you only remember one: make everything keyboard accessible.
That catches 80% of AA failures.

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.
accessible-button.htmlHTML
1
2
3
4
5
6
7
8
9
10
11
12
<!-- io.thecodeforge.accessibility -->
<!-- WRONG -->
<div onclick="submitForm()" class="btn">Submit</div>

<!-- RIGHT -->
<button type="submit" class="btn">Submit</button>

<!-- For screen reader only text -->
<button type="submit">
  Submit
  <span class="sr-only">your order</span>
</button>
Production Insight
Replacing a <button> with a <div> is a common refactoring mistake.
The <div> loses focusability, keyboard activation, and screen reader role.
Rule: if it triggers a JS action, it must be a <button>.
Key Takeaway
Native HTML wins every time.
ARIA is a polyfill, not a replacement.
Semantic markup is free accessibility.

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.
ARIA Mental Model
  • 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.
Production Insight
Over-using ARIA is more common than under-using.
Developers add role='button' to a <div> but forget tabindex and keydown.
Rule: check that every element with a role also has a name (via aria-label or visible text).
Key Takeaway
Use ARIA only when native HTML can't express the pattern.
And always test with a real screen reader.
The accessibility tree doesn't lie.

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 tabindex is set. Positive tabindex values create a confusing order — avoid them. Use tabindex="0" to make a non-focusable element focusable, and tabindex="-1" for programmatic focus only.
  • Focus indicators: Never remove :focus outline 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.
focus-styles.cssCSS
1
2
3
4
5
6
7
8
9
10
/* io.thecodeforge.accessibility */
/* Default outline removal is bad */
*:focus { outline: 2px solid #4A90D9; outline-offset: 2px; }

/* Custom focus ring for better visibility */
.button:focus-visible {
  outline: 3px solid #FFB800;
  outline-offset: 3px;
  box-shadow: 0 0 0 6px rgba(255, 184, 0, 0.3);
}
Production Insight
We shipped a modal that trapped focus inside — except the close button was outside the focus trap.
Users on screen readers couldn't close the modal and had to refresh the page.
Rule: always audit focus management with a keyboard-only session before release.
Key Takeaway
Tab order is the user's map.
Don't rearrange it. Don't remove the outline.
And always provide a visible focus indicator with 3:1 contrast.

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 is Not Just About Text
Many tools only check text contrast. WCAG 2.1 AA also requires 3:1 contrast for UI components and graphical objects. That means your submit button border, close icon, and progress bar all need sufficient contrast. Use the contrast-ratio npm package to automate checks.
Production Insight
A client's brand guidelines used light grey (#B0B0B0) for placeholder text.
Against a white background, the ratio was 2.8:1.
Users with low vision couldn't see the placeholders.
Fix: darken to #757575 (4.5:1) and use helper text instead of placeholders.
Key Takeaway
Contrast is a measurable, testable requirement.
Don't rely on your eyes — use a tool.
4.5:1 for text, 3:1 for everything else.

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.
ci-accessibility-check.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// io.thecodeforge.testing
const { axe } = require('axe-core');
const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto('https://your-site.com');
  const results = await axe.run(page);
  if (results.violations.length > 0) {
    console.error('Accessibility violations found:', results.violations.length);
    process.exit(1);
  }
  console.log('No violations — accessible!');
  await browser.close();
})();
Production Insight
Running aXe only once before release is not enough.
We added a React component that injected dynamic content without ARIA live regions.
Automated CI caught it on every commit.
Rule: make accessibility testing a build step, not a manual ticket.
Key Takeaway
Automation + manual testing = confidence.
aXe catches low-hanging fruit.
Keyboard and screen reader testing catch the rest.

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.

AriaExample.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// io.thecodeforge
// BAD: ARIA on a non-interactive element
<span role="button" aria-label="Submit" id="fakeBtn">Click</span>

// GOOD: Native button with ARIA for context
<button id="realBtn" aria-label="Submit form">
  <svg aria-hidden="true" focusable="false">...</svg>
</button>

// JS ES2024: Ensure keyboard support for custom widget
const fakeBtn = document.getElementById('fakeBtn');
fakeBtn.addEventListener('keydown', (e) => {
  if (e.key === 'Enter' || e.key === ' ') {
    e.preventDefault();
    submitForm();
  }
});
Output
Screen reader announces 'Submit form button'. Keyboard users can tab to real <button>. Custom <span> requires JS to accept keyboard input — fragile and error-prone.
Production Trap:
Never use 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.
Key Takeaway
ARIA doesn't add behavior, only meaning. Start with semantic HTML, then layer ARIA only when the native element can't express the interface.

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.

LiveRegion.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// io.thecodeforge
// BAD: No announcement for dynamically updated content
document.getElementById('results').innerHTML = '5 items found';

// GOOD: Using a polite live region
<div id="announcement" aria-live="polite" role="status"></div>

// JS ES2024: Update live region safely
const announcer = document.getElementById('announcement');
const updateResults = (count) => {
  announcer.textContent = `${count} items found`;  // Screen reader announces instantly
};

// ES2024 feature: Using Promise.withResolvers for async updates
const { promise, resolve } = Promise.withResolvers();
setTimeout(() => {
  updateResults(12);
  resolve('Updated');
}, 1000);
Output
Screen reader announces '12 items found' without moving focus. User stays on current element, hears the update.
Production Trap:
Don't use 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').
Key Takeaway
Every dynamic update needs a live region. Wrap status messages in aria-live="polite" or role="status" so screen readers announce changes without moving focus.
● Production incidentPOST-MORTEMseverity: high

The Unclickable Checkout Button

Symptom
Users who couldn't use a mouse (motor-impaired, broken touchpad, or power keyboard users) couldn't complete purchases. Cart abandonment spiked 23% for that segment.
Assumption
The team assumed clicking worked for everyone. The button was a <div> with an onclick handler — no tabindex, no role, no keyboard listener.
Root cause
The <div> was focusable via Tab because of the onclick attribute in some browsers, but it didn't fire on Enter/Space. Screen readers announced it as a generic group, not a button.
Fix
Replaced the <div> with a <button> element. Added proper keyboard handler for Enter and Space. Tested with keyboard alone.
Key lesson
  • 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.
ConceptUse CaseExample
Web Accessibility — WCAG BasicsCore usageSee code above
Keyboard SupportOperable principleAll interactive elements must be reachable via Tab
Alt TextPerceivable principle<img alt='Chart showing quarterly sales'>
Contrast RatioPerceivable principleText: 4.5:1, Large text: 3:1

Key takeaways

1
You now understand what Web Accessibility
WCAG Basics is and why it exists
2
You've seen it working in a real runnable example
3
Practice daily
the forge only works when it's hot 🔥
4
WCAG is built on four principles
POUR — Perceivable, Operable, Understandable, Robust.
5
Semantic HTML is the foundation; ARIA is a supplement, not a replacement.
6
Keyboard accessibility is the highest-impact, most-tested criterion.
7
Contrast ratios are measurable
4.5:1 for text, 3:1 for UI components.
8
Automated tools catch ~30% of issues; manual testing with keyboard and screen reader is essential.

Common mistakes to avoid

4 patterns
×

Memorising syntax before understanding the concept

Symptom
You can write ARIA roles from memory but don't know when they're appropriate, leading to over-ARIAing everything.
Fix
Start with native HTML. Learn the POUR principles. Then add ARIA only for custom widgets.
×

Skipping practice and only reading theory

Symptom
You know the rules but your code still fails accessibility audits because you never tested with real users or tools.
Fix
Integrate aXe into your dev workflow. Keep a keyboard-only session every sprint. Use a screen reader once a week.
×

Using role='button' on a <div> without adding keyboard support

Symptom
Screen readers announce 'button' but pressing Enter or Space does nothing. Users get confused and leave.
Fix
Add tabindex='0', and keydown handler for Enter and Space. Better yet, use a <button> element.
×

Removing outline on focus without providing an alternative

Symptom
Keyboard users can't see where focus is. They navigate blindly, often clicking the wrong thing.
Fix
Replace the default outline with a custom focus ring that has at least 3:1 contrast against the background.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What are the four principles of WCAG? Explain each.
Q02SENIOR
How do you make a custom dropdown component accessible?
Q03SENIOR
Describe a time you fixed an accessibility bug in production. What was t...
Q01 of 03JUNIOR

What are the four principles of WCAG? Explain each.

ANSWER
The four principles are Perceivable, Operable, Understandable, and Robust (POUR). - Perceivable: information and UI must be available to users' senses. Provide text alternatives, captions, and adaptable content. - Operable: navigation and interfaces must work with keyboards, timing adjustments, and avoid triggers for seizures. - Understandable: content must be readable and predictable. Input assistance helps users avoid and correct mistakes. - Robust: code must be semantically correct and compatible with current and future assistive technologies.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
What is Web Accessibility — WCAG Basics in simple terms?
02
What's the difference between WCAG A, AA, and AAA?
03
How do I test my site for WCAG compliance?
04
Is it enough to use ARIA to make my site accessible?
N
Naren Founder & Principal Engineer

20+ years shipping production JavaScript and front-end systems at scale. Drawn from code that ran under real load.

Follow
Verified
production tested
May 24, 2026
last updated
1,554
articles · all by Naren
🔥

That's HTML & CSS. Mark it forged?

6 min read · try the examples if you haven't

Previous
CSS Preprocessors — SASS LESS
11 / 16 · HTML & CSS
Next
Tailwind CSS Complete Guide: Utility-First Styling