โ™ฟ Web Accessibility Guide

Complete A11y Reference with Interactive Examples

Why Accessibility Matters

Accessibility (a11y) ensures your website works for everyone, including people with disabilities. Over 1 billion people worldwide have some form of disability.

๐Ÿ“Š Key Statistics
  • 15% of world population has some form of disability
  • 8% of men have color blindness
  • Many users rely on keyboards, screen readers, or other assistive tech
  • Accessibility improvements benefit ALL users (better UX, SEO)

Types of Disabilities to Consider

Type Examples Assistive Tech
Visual Blindness, low vision, color blindness Screen readers, magnifiers
Motor Limited hand mobility, tremors Keyboard, voice control, switches
Auditory Deafness, hard of hearing Captions, transcripts
Cognitive Dyslexia, ADHD, learning disabilities Clear layout, simple language

VoiceOver Quick Reference (Mac)

Action Keys
Enable/Disable VoiceOver Cmd + F5
Navigate to next element Ctrl + Option + โ†’
Navigate to previous element Ctrl + Option + โ†
Open Rotor (headings, landmarks) Ctrl + Option + U
Read all from here Ctrl + Option + A
Stop reading Ctrl
โš ๏ธ Tab Key vs VoiceOver

Tab key only moves between interactive elements (buttons, links, inputs).

VoiceOver arrows (Ctrl + Option + โ†’) read ALL content including headings and paragraphs.

1. Semantic HTML

Using HTML elements for their intended purpose, not just for styling. Screen readers use semantic elements to understand page structure.

โŒ Non-Semantic (Bad)

<div class="nav"> <span>Home</span> <span>About</span> </div> <div class="heading">My Title</div> <div class="content">Text...</div>
"Home... About... My Title... Text..."

No landmarks or heading levels announced!

โœ… Semantic (Good)

<nav aria-label="Main"> <ul> <li><a href="#">Home</a></li> <li><a href="#">About</a></li> </ul> </nav> <h1>My Title</h1> <p>Text...</p>
"navigation, Main... heading level 1, My Title..."

Landmarks and heading levels announced! โœ“

Key Semantic Elements

Element Purpose VoiceOver Announces
<header> Page or section header "banner"
<nav> Navigation links "navigation"
<main> Primary content (one per page) "main"
<article> Self-contained content "article"
<aside> Sidebar, related content "complementary"
<footer> Page or section footer "content info"
<h1>-<h6> Headings (hierarchy matters!) "heading level X"
<button> Clickable actions "button"

Interactive Demo

โŒ Div "Button"

Click me (div)
  • Can't Tab to it
  • Enter/Space don't work
  • Not announced as button

โœ… Real Button

  • Tab focuses it โœ“
  • Enter/Space work โœ“
  • Announced as "button" โœ“

2. ARIA Attributes

ARIA (Accessible Rich Internet Applications) provides extra information to assistive technologies when HTML alone isn't enough.

โš ๏ธ First Rule of ARIA

Don't use ARIA if native HTML works! A real <button> is better than <div role="button">.

Common ARIA Attributes

role - What the element IS

<div role="button">Click me</div> <div role="alert">Error message</div> <div role="dialog" aria-modal="true">Modal</div>

aria-label - Accessible name (when text isn't visible)

โŒ No Label

"button" (or emoji name)

โœ… With aria-label

"Search, button"

aria-labelledby - Reference another element's ID

<h2 id="section-title">Billing Address</h2> <form aria-labelledby="section-title"> <!-- Form is labeled by the heading --> </form>

aria-describedby - Additional description

<input type="password" aria-describedby="pwd-help"> <p id="pwd-help">Must be at least 8 characters</p>

aria-hidden - Hide from screen readers

<!-- Decorative emoji hidden from screen readers --> <span aria-hidden="true">๐ŸŽ‰</span> Congratulations!

aria-live - Announce dynamic changes

โŒ Silent Update

Status updates but VoiceOver says nothing!

โœ… Announced Update

"Item added!"

3. Keyboard Navigation

All interactive elements must be usable with keyboard only (no mouse). Many users can't use a mouse.

Essential Keyboard Controls

Key Expected Action
Tab Move to next focusable element
Shift + Tab Move to previous element
Enter Activate buttons, follow links
Space Activate buttons, toggle checkboxes
Arrow keys Navigate within components (menus, tabs)
Escape Close modals, dropdowns

Making Custom Elements Keyboard Accessible

โŒ Click Only

<div class="checkbox" onclick="toggle()" ></div>
Accept terms (click only)
  • Can't Tab to it
  • Space doesn't toggle
  • Not announced as checkbox

โœ… Full Keyboard Support

<div class="checkbox" role="checkbox" tabindex="0" aria-checked="false" onclick="toggle(this)" onkeydown="handleKey(e)" ></div>
Accept terms (Tab + Space)
"unchecked, checkbox"

4. Focus Management

Users should always know where they are on the page. Focus should be visible and move logically.

Visible Focus Indicators

โŒ Focus Removed

/* NEVER do this! */ *:focus { outline: none; }

Tab to these - can't tell which is focused!

โœ… Clear Focus Ring

*:focus { outline: 3px solid #1a73e8; outline-offset: 2px; } /* Or keyboard-only focus */ *:focus-visible { outline: 3px solid #1a73e8; }

Blue ring shows exactly where you are! โœ“

Focus Trapping in Modals

When a modal is open, Tab should cycle only within the modal, not escape to the page behind.

function trapFocus(modal) { const focusable = modal.querySelectorAll( 'button, [href], input, select, textarea' ); const first = focusable[0]; const last = focusable[focusable.length - 1]; modal.addEventListener('keydown', (e) => { if (e.key === 'Tab') { if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); } else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); } } if (e.key === 'Escape') closeModal(); }); }

6. Understanding tabindex

The tabindex attribute controls whether and when an element can be focused with Tab.

The Three Values

Value Tab Key JS .focus() Use Case
tabindex="0" โœ“ Yes โœ“ Yes Add non-focusable elements to tab order
tabindex="-1" โœ— No โœ“ Yes Programmatic focus only (modals, errors)
tabindex="1+" โš ๏ธ Avoid โœ“ Yes Creates confusing order - DON'T USE

tabindex="0" - Add to Tab Order

<!-- Divs aren't focusable by default --> <div>Can't Tab here</div> <!-- Add tabindex="0" to make focusable --> <div tabindex="0">Now I'm focusable!</div> <!-- Use for custom interactive elements --> <div role="button" tabindex="0">Custom Button</div>

tabindex="-1" - Programmatic Focus Only

<!-- Can't Tab here, but JS can focus it --> <div id="error-message" tabindex="-1"> Something went wrong! </div> <!-- Focus it when error occurs --> <script> document.getElementById('error-message').focus(); </script>

Common Use Cases for tabindex="-1"

โŒ Never Use Positive tabindex
<!-- BAD - Creates confusing order --> <button tabindex="3">Third</button> <button tabindex="1">First (jumps here!)</button> <button tabindex="2">Second</button> <!-- GOOD - Use DOM order instead --> <button>First</button> <button>Second</button> <button>Third</button>

7. Form Labels & Errors

Every form input must have an associated label that screen readers can announce.

Label Association Methods

โŒ No Association

<span>Email:</span> <input type="email">
Email:
"edit text, blank"

โœ… Proper Label

<!-- Method 1: Wrap --> <label> Email: <input type="email"> </label> <!-- Method 2: for/id --> <label for="email">Email:</label> <input id="email" type="email">
"Email, edit text"

Error States

โŒ Error Not Linked

<label>Username:</label> <input type="text"> <span class="error">Required</span>

Error exists but not announced with field!

โœ… Error Linked

<label for="user">Username:</label> <input id="user" aria-invalid="true" aria-describedby="user-error" > <span id="user-error" role="alert"> Required </span>
"Username, invalid data, Required"

8. Images & Alt Text

Images need descriptive alt text so screen reader users understand the content.

Alt Text Decision Tree

Is the image purely decorative? โ”œโ”€โ”€ Yes โ†’ alt="" โ””โ”€โ”€ No โ”œโ”€โ”€ Is it a link/button? โ”‚ โ””โ”€โ”€ Describe the ACTION (alt="Search", alt="Home") โ””โ”€โ”€ Is it informative? โ””โ”€โ”€ Describe the CONTENT (alt="Chart showing 50% growth")

Examples

Type Bad Good
Informative alt="image" alt="Golden retriever playing in park"
Functional alt="magnifying glass" alt="Search"
Decorative alt="decorative swirl" alt="" (empty)
Chart/Graph alt="chart" alt="Sales increased 50% from Q1 to Q4"

9. Testing Tools

Tool Type What It Does
axe DevTools Browser extension Automated accessibility testing
WAVE Browser extension Visual overlay of issues
Lighthouse Chrome DevTools Accessibility audit score
VoiceOver macOS built-in Screen reader testing
NVDA Windows (free) Screen reader testing
Keyboard only Manual Unplug mouse, use Tab/Enter

Quick Accessibility Checklist

Check How to Test
Keyboard navigation Tab through entire page - can you reach everything?
Focus visible Can you always see where focus is?
Headings Use Rotor - logical h1โ†’h2โ†’h3 order?
Landmarks Use Rotor - nav, main, footer present?
Images Navigate to images - meaningful alt text?
Forms Focus inputs - labels announced?
Color contrast Use axe/Lighthouse - 4.5:1 ratio for text?

๐Ÿ“‹ Quick Reference

Issue Bad Good
Buttons <div onclick> <button>
Headings <div class="title"> <h1>, <h2>, <h3>
Navigation <div class="nav"> <nav>
Images <img src="..."> <img alt="description">
Forms <span>Email</span><input> <label>Email <input></label>
Icon buttons <button>๐Ÿ”</button> <button aria-label="Search">๐Ÿ”</button>
Focus outline: none outline: 3px solid blue
Dynamic updates <div id="status"> <div aria-live="polite">