Web Accessibility Reference
Comprehensive reference for WCAG 2.1/2.2, ARIA, semantic HTML, keyboard navigation, screen readers, and accessible patterns
Version: 1.0 Last Updated: 2025-10-27 Lines: ~1,900
Table of Contents
- WCAG Guidelines
- ARIA Reference
- Semantic HTML
- Keyboard Navigation
- Screen Reader Support
- Focus Management
- Color and Contrast
- Forms and Validation
- Dynamic Content
- Testing Tools and Workflows
- Common Accessible Patterns
- Mobile Accessibility
- Anti-Patterns and Fixes
WCAG Guidelines
WCAG Principles (POUR)
Perceivable: Information and user interface components must be presentable to users in ways they can perceive.
Operable: User interface components and navigation must be operable.
Understandable: Information and the operation of user interface must be understandable.
Robust: Content must be robust enough that it can be interpreted reliably by a wide variety of user agents, including assistive technologies.
Conformance Levels
Level A (Minimum)
- Basic web accessibility features
- Must be satisfied
- Example: Text alternatives for non-text content
Level AA (Recommended)
- Target for most websites and applications
- Addresses the biggest and most common barriers
- Example: Color contrast ratio of 4.5:1
Level AAA (Enhanced)
- Highest level of accessibility
- Not always achievable for all content
- Example: Color contrast ratio of 7:1
Best Practice: Target WCAG 2.1 Level AA for production applications.
WCAG 2.1 Level A Guidelines
1.1.1 Non-text Content (A)
All non-text content has a text alternative.
<!-- ❌ Bad -->
<img src="logo.png">
<!-- ✅ Good -->
<img src="logo.png" alt="Acme Corporation Logo">
<!-- ✅ Good: Decorative -->
<img src="decoration.png" alt="">
<!-- ✅ Good: Complex content -->
<img src="chart.png" alt="Sales data for Q4 2025" longdesc="chart-description.html">
1.2.1 Audio-only and Video-only (A)
Provide an alternative for time-based media.
<!-- Video alternative -->
<video controls>
<source src="video.mp4" type="video/mp4">
<track kind="descriptions" src="descriptions.vtt" srclang="en" label="English">
</video>
<details>
<summary>Transcript</summary>
<p>Full text transcript of the video...</p>
</details>
1.2.2 Captions (Prerecorded) (A)
Captions are provided for all prerecorded audio content.
<video controls>
<source src="video.mp4" type="video/mp4">
<track kind="captions" src="captions-en.vtt" srclang="en" label="English" default>
<track kind="captions" src="captions-es.vtt" srclang="es" label="Español">
</video>
1.3.1 Info and Relationships (A)
Information, structure, and relationships can be programmatically determined.
<!-- ❌ Bad: Visual structure only -->
<div>
<div style="font-size: 24px; font-weight: bold;">Section Title</div>
<div>Content here</div>
</div>
<!-- ✅ Good: Semantic structure -->
<section>
<h2>Section Title</h2>
<p>Content here</p>
</section>
1.3.2 Meaningful Sequence (A)
The reading order of content is meaningful.
<!-- ❌ Bad: Visual order != DOM order -->
<div style="display: flex; flex-direction: column-reverse;">
<div>Second (appears first)</div>
<div>First (appears second)</div>
</div>
<!-- ✅ Good: DOM order matches visual order -->
<div style="display: flex; flex-direction: column;">
<div>First</div>
<div>Second</div>
</div>
1.3.3 Sensory Characteristics (A)
Instructions don't rely solely on sensory characteristics.
<!-- ❌ Bad: Shape-only reference -->
<p>Click the round button to continue</p>
<!-- ✅ Good: Multiple characteristics -->
<p>Click the "Continue" button (round, blue) to proceed</p>
<!-- ✅ Better: Direct reference -->
<button id="continue-btn">Continue</button>
1.4.1 Use of Color (A)
Color is not the only visual means of conveying information.
<!-- ❌ Bad: Color only -->
<span style="color: red;">Error</span>
<span style="color: green;">Success</span>
<!-- ✅ Good: Color + icon + text -->
<span style="color: red;">
<svg aria-hidden="true"><use href="#error-icon"/></svg>
Error: Field is required
</span>
1.4.2 Audio Control (A)
If audio plays automatically for more than 3 seconds, provide a mechanism to pause/stop it.
<audio id="background-music" autoplay loop>
<source src="music.mp3" type="audio/mp3">
</audio>
<button
Pause Background Music
</button>
2.1.1 Keyboard (A)
All functionality is available from a keyboard.
<!-- ❌ Bad: Mouse-only interaction -->
<div me</div>
<!-- ✅ Good: Keyboard accessible -->
<button
>
Show tooltip
</button>
2.1.2 No Keyboard Trap (A)
Keyboard focus can be moved away from a component using only the keyboard.
// ✅ Good: Allow escape from modal
function Modal({ onClose }) {
useEffect(() => {
const handleEscape = (e) => {
if (e.key === 'Escape') onClose();
};
document.addEventListener('keydown', handleEscape);
return () => document.removeEventListener('keydown', handleEscape);
}, [onClose]);
}
2.1.4 Character Key Shortcuts (A - WCAG 2.1)
If a keyboard shortcut uses only character keys, then at least one of the following is true:
- Can be turned off
- Can be remapped
- Only active when component has focus
// ✅ Good: Scoped shortcuts
function Editor() {
const editorRef = useRef();
const handleKeyPress = (e) => {
if (!editorRef.current.contains(document.activeElement)) {
return; // Only active when editor has focus
}
if (e.key === 's' && e.ctrlKey) {
e.preventDefault();
save();
}
};
return <div ref={editorRef}
}
2.2.1 Timing Adjustable (A)
User can turn off, adjust, or extend time limits.
<div role="alert">
<p>Your session will expire in 60 seconds.</p>
<button Session</button>
</div>
2.2.2 Pause, Stop, Hide (A)
Provide mechanism to pause, stop, or hide moving, blinking, or auto-updating content.
<div class="carousel">
<button aria-label="Pause carousel">⏸</button>
<button aria-label="Play carousel">▶</button>
<!-- Carousel items -->
</div>
2.3.1 Three Flashes or Below Threshold (A)
Content does not flash more than three times per second.
/* ❌ Bad: Rapid flashing */
@keyframes flash {
0%, 50% { opacity: 1; }
25%, 75% { opacity: 0; }
}
/* ✅ Good: Slow transition */
@keyframes fade {
0% { opacity: 0; }
100% { opacity: 1; }
}
2.4.1 Bypass Blocks (A)
A mechanism is available to bypass blocks of content that are repeated.
<!-- Skip navigation link -->
<a href="#main-content" class="skip-link">Skip to main content</a>
<nav><!-- Navigation --></nav>
<main id="main-content">
<!-- Main content -->
</main>
<style>
.skip-link {
position: absolute;
left: -9999px;
top: 0;
}
.skip-link:focus {
left: 0;
top: 0;
z-index: 9999;
}
</style>
2.4.2 Page Titled (A)
Web pages have titles that describe topic or purpose.
<head>
<title>User Profile - Settings | Acme Corp</title>
</head>
2.4.3 Focus Order (A)
Focusable components receive focus in an order that preserves meaning.
<!-- ✅ Good: Logical focus order -->
<form>
<label for="first-name">First Name</label>
<input id="first-name" type="text">
<label for="last-name">Last Name</label>
<input id="last-name" type="text">
<button type="submit">Submit</button>
</form>
2.4.4 Link Purpose (In Context) (A)
The purpose of each link can be determined from the link text alone.
<!-- ❌ Bad: Non-descriptive -->
<a href="report.pdf">Click here</a>
<!-- ✅ Good: Descriptive -->
<a href="report.pdf">Download 2025 Annual Report (PDF, 2MB)</a>
<!-- ✅ Good: Context -->
<h2>2025 Annual Report</h2>
<p>Our annual financial summary.</p>
<a href="report.pdf">Download (PDF, 2MB)</a>
2.5.1 Pointer Gestures (A - WCAG 2.1)
All functionality that uses multipoint or path-based gestures can be operated with a single pointer.
// ✅ Good: Single-pointer alternative
function ImageZoom() {
return (
<div>
{/* Pinch to zoom alternative */}
<button In</button>
<button Out</button>
<img src="photo.jpg" alt="Landscape" />
</div>
);
}
2.5.2 Pointer Cancellation (A - WCAG 2.1)
For functionality that can be operated using a single pointer:
- No down-event trigger
- Abort or undo available
- Up-event reverses down-event
- Essential to trigger on down-event
// ✅ Good: Up-event trigger
<button me</button>
// ❌ Bad: Down-event trigger (avoid unless essential)
<button me</button>
2.5.3 Label in Name (A - WCAG 2.1)
The accessible name contains the visible text label.
<!-- ❌ Bad: Mismatch -->
<button aria-label="Submit form">Send</button>
<!-- ✅ Good: Match -->
<button aria-label="Send message">Send</button>
<!-- ✅ Better: No aria-label needed -->
<button>Send</button>
2.5.4 Motion Actuation (A - WCAG 2.1)
Functionality triggered by device motion can also be operated by user interface components.
// ✅ Good: Motion + button alternative
function ShakeToRefresh() {
return (
<>
{/* Shake to refresh */}
<button
</>
);
}
3.1.1 Language of Page (A)
The default human language of each page can be programmatically determined.
<html lang="en">
<head>
<title>Welcome</title>
</head>
<body>
<p>This is English content.</p>
<p lang="es">Este es contenido en español.</p>
</body>
</html>
3.2.1 On Focus (A)
When a component receives focus, it does not initiate a change of context.
// ❌ Bad: Auto-submit on focus
<input />
// ✅ Good: Explicit action required
<input />
<button
3.2.2 On Input (A)
Changing the setting of a user interface component does not automatically cause a change of context.
// ❌ Bad: Auto-submit on change
<select
<option>Option 1</option>
<option>Option 2</option>
</select>
// ✅ Good: Explicit action
<select
<option>Option 1</option>
<option>Option 2</option>
</select>
<button
3.3.1 Error Identification (A)
If an input error is automatically detected, the item in error is identified and described to the user in text.
<label for="email">Email</label>
<input
id="email"
type="email"
aria-invalid="true"
aria-describedby="email-error"
>
<span id="email-error" role="alert">
Error: Please enter a valid email address
</span>
3.3.2 Labels or Instructions (A)
Labels or instructions are provided when content requires user input.
<label for="password">
Password
<span aria-label="required">*</span>
</label>
<input
id="password"
type="password"
aria-describedby="password-hint"
required
>
<p id="password-hint">
Must be at least 8 characters with one uppercase letter and one number
</p>
4.1.1 Parsing (A)
In content implemented using markup languages, elements have complete start and end tags, are nested correctly, do not contain duplicate attributes, and IDs are unique.
<!-- ❌ Bad: Duplicate IDs -->
<div id="content">First</div>
<div id="content">Second</div>
<!-- ✅ Good: Unique IDs -->
<div id="content-1">First</div>
<div id="content-2">Second</div>
4.1.2 Name, Role, Value (A)
For all user interface components, the name and role can be programmatically determined.
<!-- ❌ Bad: No role/name -->
<div
<!-- ✅ Good: Proper role/name -->
<button
aria-label="Toggle menu"
aria-expanded="false"
aria-controls="menu"
>
Menu
</button>
WCAG 2.1 Level AA Guidelines
1.2.4 Captions (Live) (AA)
Captions are provided for all live audio content.
<!-- Live streaming with captions -->
<video controls>
<source src="livestream.m3u8" type="application/x-mpegURL">
<track kind="captions" src="live-captions.vtt" srclang="en" label="English">
</video>
1.2.5 Audio Description (Prerecorded) (AA)
Audio description is provided for all prerecorded video content.
<video controls>
<source src="video.mp4" type="video/mp4">
<track kind="descriptions" src="audio-description.vtt" srclang="en" label="Audio Description">
</video>
1.3.4 Orientation (AA - WCAG 2.1)
Content does not restrict its view and operation to a single display orientation.
/* ✅ Good: Support both orientations */
@media (orientation: portrait) {
.content { flex-direction: column; }
}
@media (orientation: landscape) {
.content { flex-direction: row; }
}
1.3.5 Identify Input Purpose (AA - WCAG 2.1)
The purpose of each input field can be programmatically determined when the input field serves a purpose from the autocomplete list.
<label for="email">Email</label>
<input
id="email"
type="email"
autocomplete="email"
name="email"
>
<label for="street">Street Address</label>
<input
id="street"
type="text"
autocomplete="street-address"
name="street"
>
1.4.3 Contrast (Minimum) (AA)
Text has a contrast ratio of at least 4.5:1 (or 3:1 for large text).
/* ❌ Bad: Insufficient contrast (2.8:1) */
.text {
color: #999999;
background-color: #ffffff;
}
/* ✅ Good: Sufficient contrast (4.5:1) */
.text {
color: #767676;
background-color: #ffffff;
}
/* ✅ Good: Large text (3:1) */
.large-text {
font-size: 18pt;
color: #949494;
background-color: #ffffff;
}
1.4.4 Resize Text (AA)
Text can be resized up to 200% without loss of content or functionality.
/* ✅ Good: Use relative units */
.text {
font-size: 1rem; /* Not px */
line-height: 1.5;
}
/* ✅ Good: Responsive containers */
.container {
max-width: 100%;
overflow: auto;
}
1.4.5 Images of Text (AA)
Use actual text rather than images of text.
<!-- ❌ Bad: Text in image -->
<img src="heading.png" alt="Welcome to our site">
<!-- ✅ Good: Actual text -->
<h1>Welcome to our site</h1>
1.4.10 Reflow (AA - WCAG 2.1)
Content can be presented without loss of information or functionality at 320 CSS pixels width.
/* ✅ Good: Responsive design */
.content {
max-width: 100%;
word-wrap: break-word;
}
@media (max-width: 320px) {
.sidebar {
display: block;
width: 100%;
}
}
1.4.11 Non-text Contrast (AA - WCAG 2.1)
Visual presentation of UI components and graphical objects have a contrast ratio of at least 3:1.
/* ✅ Good: Button with sufficient contrast */
.button {
background-color: #0066cc; /* 3:1 against white */
border: 2px solid #0052a3;
color: #ffffff;
}
.button:focus {
outline: 2px solid #0052a3; /* 3:1 against background */
}
1.4.12 Text Spacing (AA - WCAG 2.1)
No loss of content or functionality when text spacing is adjusted.
/* User may apply these adjustments */
* {
line-height: 1.5 !important;
letter-spacing: 0.12em !important;
word-spacing: 0.16em !important;
}
p {
margin-bottom: 2em !important;
}
/* ✅ Good: Design accommodates text spacing */
.container {
padding: 1em;
min-height: fit-content;
}
1.4.13 Content on Hover or Focus (AA - WCAG 2.1)
Additional content triggered by hover or focus is dismissible, hoverable, and persistent.
// ✅ Good: Accessible tooltip
function Tooltip({ trigger, content }) {
const [isVisible, setIsVisible] = useState(false);
return (
<div>
<button
=> setIsVisible(true)}
=> setIsVisible(true)}
=> setIsVisible(false)}
=> setIsVisible(false)}
aria-describedby="tooltip"
>
{trigger}
</button>
{isVisible && (
<div
id="tooltip"
role="tooltip"
=> setIsVisible(true)}
>
{content}
</div>
)}
</div>
);
}
2.4.5 Multiple Ways (AA)
More than one way is available to locate a page within a set of pages.
<!-- Navigation menu -->
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/products">Products</a></li>
</ul>
</nav>
<!-- Search -->
<form role="search">
<input type="search" aria-label="Search site">
<button type="submit">Search</button>
</form>
<!-- Site map -->
<a href="/sitemap">Site Map</a>
2.4.6 Headings and Labels (AA)
Headings and labels describe topic or purpose.
<!-- ✅ Good: Descriptive headings -->
<h1>User Account Settings</h1>
<h2>Privacy Preferences</h2>
<h3>Email Notifications</h3>
<!-- ✅ Good: Descriptive labels -->
<label for="email-frequency">How often would you like to receive emails?</label>
<select id="email-frequency">
<option>Daily</option>
<option>Weekly</option>
<option>Monthly</option>
</select>
2.4.7 Focus Visible (AA)
Keyboard focus indicator is visible.
/* ❌ Bad: Removing focus indicator */
:focus {
outline: none;
}
/* ✅ Good: Visible focus indicator */
:focus {
outline: 2px solid #0066cc;
outline-offset: 2px;
}
/* ✅ Good: Custom focus style */
button:focus-visible {
box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.5);
outline: 2px solid #0066cc;
}
3.1.2 Language of Parts (AA)
The human language of each passage or phrase can be programmatically determined.
<p>
The word <span lang="fr">rendezvous</span> is French.
</p>
<blockquote lang="es">
<p>La vida es bella.</p>
</blockquote>
3.2.3 Consistent Navigation (AA)
Navigational mechanisms that are repeated on multiple pages occur in the same relative order.
<!-- Same navigation on every page -->
<nav aria-label="Main">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
3.2.4 Consistent Identification (AA)
Components that have the same functionality are identified consistently.
<!-- ✅ Good: Consistent icon usage -->
<button aria-label="Close dialog">
<svg><use href="#close-icon"/></svg>
</button>
<!-- Same icon/label in different context -->
<button aria-label="Close panel">
<svg><use href="#close-icon"/></svg>
</button>
3.3.3 Error Suggestion (AA)
If an input error is detected and suggestions for correction are known, then the suggestions are provided.
<label for="username">Username</label>
<input
id="username"
type="text"
aria-invalid="true"
aria-describedby="username-error"
>
<span id="username-error" role="alert">
Username must be 3-20 characters. Try: johndoe123
</span>
3.3.4 Error Prevention (Legal, Financial, Data) (AA)
For pages that cause legal commitments or financial transactions, submissions are reversible, checked, or confirmed.
<form>
<h2>Payment Details</h2>
<!-- Form fields -->
<button type="button"
Review Order
</button>
</form>
<div id="confirmation" role="dialog" aria-labelledby="confirm-title">
<h2 id="confirm-title">Confirm Your Order</h2>
<!-- Order summary -->
<button and Pay</button>
<button Order</button>
</div>
4.1.3 Status Messages (AA - WCAG 2.1)
Status messages can be programmatically determined through role or properties.
<!-- Success message -->
<div role="status" aria-live="polite">
Your changes have been saved.
</div>
<!-- Error alert -->
<div role="alert" aria-live="assertive">
Error: Unable to save changes.
</div>
<!-- Progress indicator -->
<div role="status" aria-live="polite" aria-atomic="true">
Uploading: 45% complete
</div>
WCAG 2.1 Level AAA Guidelines (Selected)
1.2.6 Sign Language (Prerecorded) (AAA)
Sign language interpretation is provided for all prerecorded audio content.
1.2.7 Extended Audio Description (Prerecorded) (AAA)
Where pauses in foreground audio are insufficient for audio descriptions to convey the sense of the video, extended audio description is provided.
1.4.6 Contrast (Enhanced) (AAA)
Text has a contrast ratio of at least 7:1 (or 4.5:1 for large text).
/* ✅ AAA: Enhanced contrast (7:1) */
.text {
color: #595959;
background-color: #ffffff;
}
1.4.8 Visual Presentation (AAA)
For text presentation:
- Width is no more than 80 characters
- Text is not justified
- Line spacing is at least 1.5
- Paragraph spacing is at least 2x line spacing
/* ✅ AAA: Optimal reading */
.text {
max-width: 80ch;
line-height: 1.5;
text-align: left; /* Not justified */
}
.text p {
margin-bottom: 3em; /* 2x line height */
}
2.1.3 Keyboard (No Exception) (AAA)
All functionality is available from a keyboard without exception.
2.4.9 Link Purpose (Link Only) (AAA)
The purpose of each link can be identified from link text alone.
<!-- ✅ AAA: No context needed -->
<a href="report.pdf">Download 2025 Annual Report (PDF, 2MB)</a>
2.4.10 Section Headings (AAA)
Section headings are used to organize the content.
<article>
<h1>Article Title</h1>
<section>
<h2>Introduction</h2>
<p>...</p>
</section>
<section>
<h2>Methods</h2>
<p>...</p>
</section>
<section>
<h2>Results</h2>
<p>...</p>
</section>
</article>
ARIA Reference
ARIA Roles
Landmark Roles
banner: Site header with logo, site title
<header role="banner">
<h1>Site Name</h1>
</header>
navigation: Collection of navigational links
<nav role="navigation" aria-label="Main">
<ul>
<li><a href="/">Home</a></li>
</ul>
</nav>
main: Main content of the document
<main role="main">
<article>...</article>
</main>
complementary: Supporting content (sidebar)
<aside role="complementary">
<h2>Related Links</h2>
</aside>
contentinfo: Footer information
<footer role="contentinfo">
<p>© 2025 Company</p>
</footer>
search: Search functionality
<form role="search">
<input type="search" aria-label="Search">
<button type="submit">Search</button>
</form>
region: Significant section of content
<section role="region" aria-labelledby="region-title">
<h2 id="region-title">Special Offers</h2>
</section>
form: Landmark containing form controls
<form role="form" aria-labelledby="form-title">
<h2 id="form-title">Contact Us</h2>
</form>
Widget Roles
button: Clickable element that triggers a response
<div role="button" tabindex="0">Custom Button</div>
<!-- Prefer: <button>Custom Button</button> -->
checkbox: Checkable input with three states: true, false, mixed
<div role="checkbox" aria-checked="true" tabindex="0">
Accept terms
</div>
radio: Checkable input in a group of radio roles
<div role="radiogroup" aria-labelledby="group-label">
<span id="group-label">Size</span>
<div role="radio" aria-checked="true" tabindex="0">Small</div>
<div role="radio" aria-checked="false" tabindex="-1">Large</div>
</div>
textbox: Input that allows free-form text
<div role="textbox" contenteditable="true" aria-label="Note"></div>
link: Interactive reference to a resource
<span role="link" tabindex="0" here</span>
<!-- Prefer: <a href="...">Click here</a> -->
menubar: Presentation of a menu in a horizontal bar
<ul role="menubar">
<li role="menuitem"><a href="/file">File</a></li>
<li role="menuitem"><a href="/edit">Edit</a></li>
</ul>
menu: List of choices presented to the user
<ul role="menu">
<li role="menuitem">Save</li>
<li role="menuitem">Save As...</li>
<li role="separator"></li>
<li role="menuitem">Exit</li>
</ul>
menuitem: Option in a menu
menuitemcheckbox: Checkable menuitem
<ul role="menu">
<li role="menuitemcheckbox" aria-checked="true">Show toolbar</li>
</ul>
menuitemradio: Checkable menuitem in group of menuitems
<ul role="menu">
<li role="menuitemradio" aria-checked="true">Small</li>
<li role="menuitemradio" aria-checked="false">Large</li>
</ul>
option: Selectable item in a listbox
<div role="listbox" aria-label="Colors">
<div role="option" aria-selected="true">Red</div>
<div role="option" aria-selected="false">Blue</div>
</div>
progressbar: Element that displays progress status
<div
role="progressbar"
aria-valuenow="75"
aria-valuemin="0"
aria-valuemax="100"
aria-label="Upload progress"
>
75%
</div>
scrollbar: Graphical object that controls scrolling
<div
role="scrollbar"
aria-controls="content"
aria-valuenow="50"
aria-valuemin="0"
aria-valuemax="100"
aria-orientation="vertical"
tabindex="0"
></div>
slider: Input where user selects a value from a range
<div
role="slider"
aria-valuenow="50"
aria-valuemin="0"
aria-valuemax="100"
aria-label="Volume"
tabindex="0"
></div>
spinbutton: Form of range with increase/decrease buttons
<div
role="spinbutton"
aria-valuenow="5"
aria-valuemin="0"
aria-valuemax="10"
aria-label="Quantity"
tabindex="0"
></div>
switch: Checkbox that represents on/off values
<button
role="switch"
aria-checked="true"
aria-label="Enable notifications"
>
<span>On</span>
</button>
tab: Tab in a tablist
<div role="tablist" aria-label="Sections">
<button role="tab" aria-selected="true" aria-controls="panel1">
Tab 1
</button>
<button role="tab" aria-selected="false" aria-controls="panel2">
Tab 2
</button>
</div>
<div role="tabpanel" id="panel1">Content 1</div>
<div role="tabpanel" id="panel2" hidden>Content 2</div>
tablist: List of tab elements
tabpanel: Container for resources associated with a tab
combobox: Composite widget with input and popup
<div role="combobox" aria-expanded="false" aria-haspopup="listbox">
<input type="text" aria-autocomplete="list" aria-controls="listbox">
<ul role="listbox" id="listbox" hidden>
<li role="option">Option 1</li>
<li role="option">Option 2</li>
</ul>
</div>
grid: Composite widget containing cells of tabular data
<div role="grid" aria-labelledby="grid-title">
<div role="row">
<div role="columnheader">Name</div>
<div role="columnheader">Age</div>
</div>
<div role="row">
<div role="gridcell">John</div>
<div role="gridcell">30</div>
</div>
</div>
listbox: Widget that allows user to select one or more items
<ul role="listbox" aria-label="Fruits">
<li role="option" aria-selected="true">Apple</li>
<li role="option" aria-selected="false">Banana</li>
</ul>
tree: Widget that allows user to select items from hierarchical list
<ul role="tree" aria-label="File system">
<li role="treeitem" aria-expanded="true">
Documents
<ul role="group">
<li role="treeitem">Resume.pdf</li>
</ul>
</li>
</ul>
treegrid: Grid whose rows can be expanded and collapsed
treeitem: Item in a tree
Document Structure Roles
article: Self-contained composition
<article role="article">
<h2>Article Title</h2>
<p>Content...</p>
</article>
definition: Definition of a term or concept
<dl>
<dt>Accessibility</dt>
<dd role="definition">The practice of making content usable by all people</dd>
</dl>
directory: List of references to members of a group
document: Content that contains primarily static information
feed: Scrollable list of articles
figure: Perceivable content with optional caption
<figure role="figure" aria-labelledby="fig-caption">
<img src="chart.png" alt="">
<figcaption id="fig-caption">Sales data for 2025</figcaption>
</figure>
group: Set of user interface objects
<div role="group" aria-labelledby="group-title">
<h3 id="group-title">Shipping Address</h3>
<input type="text" aria-label="Street">
<input type="text" aria-label="City">
</div>
heading: Heading for a section of the page
<div role="heading" aria-level="2">Section Title</div>
<!-- Prefer: <h2>Section Title</h2> -->
img: Container for a collection of elements that form an image
<div role="img" aria-label="Company logo">
<svg>...</svg>
</div>
list: Group of non-interactive list items
<div role="list">
<div role="listitem">Item 1</div>
<div role="listitem">Item 2</div>
</div>
<!-- Prefer: <ul><li>Item 1</li><li>Item 2</li></ul> -->
listitem: Single item in a list
math: Mathematical expression
<div role="math" aria-label="Pythagorean theorem">
a² + b² = c²
</div>
note: Parenthetic or ancillary content
<aside role="note" aria-label="Editor's note">
<p>This article was updated on 2025-10-27.</p>
</aside>
presentation/none: Element whose semantics should be removed
<table role="presentation">
<tr>
<td>Layout cell</td>
</tr>
</table>
separator: Divider between sections
<hr role="separator">
<div role="separator" aria-orientation="horizontal"></div>
table: Non-interactive table
<div role="table" aria-labelledby="table-title">
<div id="table-title">Employee List</div>
<div role="rowgroup">
<div role="row">
<div role="columnheader">Name</div>
<div role="columnheader">Title</div>
</div>
</div>
<div role="rowgroup">
<div role="row">
<div role="cell">John Doe</div>
<div role="cell">Engineer</div>
</div>
</div>
</div>
<!-- Prefer: <table>...</table> -->
term: Word or phrase with an optional corresponding definition
<span role="term" aria-describedby="def1">ARIA</span>
<span id="def1">Accessible Rich Internet Applications</span>
toolbar: Collection of commonly used controls
<div role="toolbar" aria-label="Text formatting">
<button aria-label="Bold">B</button>
<button aria-label="Italic">I</button>
<button aria-label="Underline">U</button>
</div>
tooltip: Contextual popup that displays information
<button aria-describedby="tooltip1">Help</button>
<div role="tooltip" id="tooltip1">
Click for more information
</div>
Live Region Roles
alert: Important, time-sensitive message
<div role="alert">
Error: Your session has expired
</div>
log: Live region where new information is added
<div role="log" aria-live="polite" aria-atomic="false">
<p>User joined: Alice</p>
<p>User joined: Bob</p>
</div>
marquee: Live region with non-essential information that changes
<div role="marquee" aria-live="off">
Latest news: ...
</div>
status: Advisory information for user
<div role="status" aria-live="polite">
Changes saved successfully
</div>
timer: Numerical counter or countdown
<div role="timer" aria-live="off" aria-atomic="true">
Time remaining: 5:00
</div>
Window Roles
alertdialog: Dialog that contains an alert message
<div role="alertdialog" aria-modal="true" aria-labelledby="alert-title">
<h2 id="alert-title">Confirm Delete</h2>
<p>Are you sure you want to delete this item?</p>
<button>Delete</button>
<button>Cancel</button>
</div>
dialog: Application window designed to interrupt
<div role="dialog" aria-modal="true" aria-labelledby="dialog-title">
<h2 id="dialog-title">Settings</h2>
<!-- Dialog content -->
</div>
ARIA States and Properties
Widget Attributes
aria-autocomplete: Indicates autocomplete behavior
<input
type="text"
role="combobox"
aria-autocomplete="list"
aria-controls="suggestions"
>
Values: none, inline, list, both
aria-checked: Indicates checked state
<div role="checkbox" aria-checked="true" tabindex="0">
Option 1
</div>
Values: true, false, mixed
aria-disabled: Indicates element is disabled
<button aria-disabled="true">Submit</button>
Values: true, false
aria-expanded: Indicates expanded state
<button aria-expanded="false" aria-controls="menu">
Menu
</button>
<ul id="menu" hidden>...</ul>
Values: true, false, undefined
aria-haspopup: Indicates element triggers popup
<button aria-haspopup="menu">Options</button>
Values: false, true, menu, listbox, tree, grid, dialog
aria-hidden: Indicates element is hidden from accessibility tree
<span aria-hidden="true">★</span>
<span class="sr-only">5 stars</span>
Values: true, false, undefined
aria-invalid: Indicates invalid value
<input
type="email"
aria-invalid="true"
aria-describedby="email-error"
>
<span id="email-error">Please enter a valid email</span>
Values: true, false, grammar, spelling
aria-label: Defines accessible name
<button aria-label="Close dialog">×</button>
aria-level: Defines hierarchical level
<div role="heading" aria-level="2">Subsection</div>
aria-modal: Indicates modal dialog
<div role="dialog" aria-modal="true">...</div>
Values: true, false
aria-multiline: Indicates multiline text input
<div role="textbox" aria-multiline="true" contenteditable>
</div>
Values: true, false
aria-multiselectable: Indicates multiple selection allowed
<ul role="listbox" aria-multiselectable="true">
<li role="option">Option 1</li>
<li role="option">Option 2</li>
</ul>
Values: true, false
aria-orientation: Indicates orientation
<div role="scrollbar" aria-orientation="vertical">
</div>
Values: horizontal, vertical, undefined
aria-placeholder: Defines placeholder text
<div
role="textbox"
contenteditable
aria-placeholder="Enter text here"
>
</div>
aria-pressed: Indicates pressed state of toggle button
<button aria-pressed="true">Bold</button>
Values: true, false, mixed, undefined
aria-readonly: Indicates element is not editable
<input type="text" aria-readonly="true" value="Read only">
Values: true, false
aria-required: Indicates required field
<input
type="text"
aria-required="true"
aria-label="Email (required)"
>
Values: true, false
aria-selected: Indicates selected state
<div role="option" aria-selected="true">Option 1</div>
Values: true, false, undefined
aria-sort: Indicates sort order
<th role="columnheader" aria-sort="ascending">Name</th>
Values: ascending, descending, none, other
aria-valuemax: Maximum value
<div
role="slider"
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="50"
>
</div>
aria-valuemin: Minimum value
aria-valuenow: Current value
aria-valuetext: Human-readable value text
<div
role="slider"
aria-valuenow="2"
aria-valuetext="Medium"
>
</div>
Live Region Attributes
aria-atomic: Indicates if entire region should be announced
<div role="status" aria-live="polite" aria-atomic="true">
Loading: 50%
</div>
Values: true, false
aria-busy: Indicates element is being modified
<div aria-busy="true">Loading...</div>
Values: true, false
aria-live: Indicates live region update priority
<div aria-live="polite">Status message</div>
Values: off, polite, assertive
aria-relevant: Indicates what changes should be announced
<div
role="log"
aria-live="polite"
aria-relevant="additions text"
>
</div>
Values: additions, removals, text, all
Relationship Attributes
aria-activedescendant: Identifies active descendant
<div
role="combobox"
aria-activedescendant="option-2"
>
<input type="text">
<ul role="listbox">
<li role="option" id="option-1">Option 1</li>
<li role="option" id="option-2">Option 2</li>
</ul>
</div>
aria-colcount: Defines total number of columns
<div role="table" aria-colcount="10">
<!-- Only showing 3 of 10 columns -->
<div role="row">
<div role="cell" aria-colindex="1">Cell 1</div>
<div role="cell" aria-colindex="2">Cell 2</div>
<div role="cell" aria-colindex="3">Cell 3</div>
</div>
</div>
aria-colindex: Defines column index
aria-colspan: Defines number of columns spanned
aria-controls: Identifies controlled elements
<button aria-expanded="false" aria-controls="dropdown">
Expand
</button>
<div id="dropdown" hidden>Content</div>
aria-describedby: References descriptive elements
<input
type="password"
aria-describedby="password-hint"
>
<p id="password-hint">Must be at least 8 characters</p>
aria-details: References detailed description
<img
src="chart.png"
alt="Sales chart"
aria-details="chart-description"
>
<div id="chart-description">
<h3>Detailed Description</h3>
<p>The chart shows...</p>
</div>
aria-errormessage: References error message
<input
type="email"
aria-invalid="true"
aria-errormessage="email-error"
>
<span id="email-error" role="alert">
Please enter a valid email address
</span>
aria-flowto: Identifies next element in alternate reading order
<div id="step1" aria-flowto="step2">Step 1</div>
<div id="step2">Step 2</div>
aria-labelledby: References labeling elements
<div role="dialog" aria-labelledby="dialog-title">
<h2 id="dialog-title">Confirm Action</h2>
</div>
aria-owns: Identifies owned elements
<div role="listbox" aria-owns="option1 option2">
<div role="option" id="option1">Option 1</div>
</div>
<div role="option" id="option2">Option 2 (elsewhere in DOM)</div>
aria-posinset: Defines position in set
<div role="listitem" aria-posinset="2" aria-setsize="10">
Item 2 of 10
</div>
aria-rowcount: Defines total number of rows
aria-rowindex: Defines row index
aria-rowspan: Defines number of rows spanned
aria-setsize: Defines total number of items in set
<div role="listitem" aria-posinset="1" aria-setsize="5">
Item 1 of 5
</div>
Semantic HTML
Document Structure
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Page Title - Site Name</title>
</head>
<body>
<!-- Skip link -->
<a href="#main-content" class="skip-link">Skip to main content</a>
<!-- Site header -->
<header>
<div class="logo">
<img src="logo.png" alt="Company Name">
</div>
<!-- Main navigation -->
<nav aria-label="Main">
<ul>
<li><a
…(truncated)