name: aria-apg-patterns description: Accessibility patterns reference based on W3C WAI-ARIA Authoring Practices Guide for implementing accessible web UI components.
ARIA APG Patterns
Accessibility patterns reference based on W3C WAI-ARIA Authoring Practices Guide
This document provides ARIA pattern guidelines for implementing accessible web UI components.
Quick Reference
| Pattern | Use Case | Required Roles |
|---|---|---|
| Accordion | Expandable/collapsible sections | button, region |
| Alert | Important message (non-modal) | alert |
| Alert Dialog | Important message (modal) | alertdialog |
| Breadcrumb | Hierarchical navigation | navigation |
| Button | Action trigger | button |
| Carousel | Slide content | region, group |
| Checkbox | Check selection | checkbox |
| Combobox | Autocomplete input | combobox |
| Dialog | Modal dialog | dialog |
| Disclosure | Show/hide content | button |
| Feed | Infinite scroll content | feed, article |
| Grid | Interactive tabular data | grid, row, gridcell |
| Link | Resource reference | link |
| Listbox | Option list selection | listbox, option |
| Menu | Action/function selection | menu, menuitem |
| Menu Button | Menu trigger button | button |
| Meter | Value within range display | meter |
| Radio Group | Single selection | radiogroup, radio |
| Slider | Range value selection | slider |
| Spinbutton | Discrete value input | spinbutton |
| Switch | On/off toggle | switch |
| Table | Static table | table, row, cell |
| Tabs | Tab panel switching | tablist, tab, tabpanel |
| Tooltip | Supplementary info popup | tooltip |
| Tree View | Hierarchical list | tree, treeitem |
Accordion
Vertically stacked set of interactive headings. Each section can be expanded or collapsed.
Keyboard Interaction
| Key | Action |
|---|---|
Enter / Space |
Toggle panel expand/collapse |
Tab |
Move to next focusable element |
↓ / ↑ |
(Optional) Move focus between headers |
Home / End |
(Optional) Move to first/last header |
ARIA Attributes
<!-- Header button -->
<h3>
<button aria-expanded="true|false" aria-controls="panel-id">Section Title</button>
</h3>
<!-- Panel -->
<div id="panel-id" role="region" aria-labelledby="header-id">Content</div>
Checklist
- Header contains
buttonrole - Manage
aria-expandedstate - Connect panel with
aria-controls - Panel has
role="region"(when 6 or fewer panels expanded) - Panel has
aria-labelledbyreferencing header
Alert
Displays important message without interrupting user's current task. Does not move focus.
ARIA Attributes
<div role="alert">Important message content</div>
Checklist
- Set
role="alert" - Dynamically insert into DOM for screen reader announcement
- No focus movement (use Alert Dialog if focus needed)
- Avoid auto-dismiss (WCAG 2.2.3 compliance)
- Minimize frequent alerts
Alert Dialog
Modal dialog that interrupts user to deliver important message.
Keyboard Interaction
| Key | Action |
|---|---|
Tab |
Cycle focus within dialog |
Shift+Tab |
Reverse cycle focus |
Escape |
Close dialog |
ARIA Attributes
<div role="alertdialog" aria-modal="true" aria-labelledby="title-id" aria-describedby="desc-id">
<h2 id="title-id">Title</h2>
<p id="desc-id">Description</p>
<button>Confirm</button>
</div>
Checklist
- Set
role="alertdialog" - Set
aria-modal="true" - Connect title with
aria-labelledby - Move focus to internal element on open
- Return focus to trigger element on close
- Implement focus trap
Breadcrumb
Displays parent page links of current page in hierarchical order.
ARIA Attributes
<nav aria-label="Breadcrumb">
<ol>
<li><a href="/">Home</a></li>
<li><a href="/products">Products</a></li>
<li><a href="/products/shoes" aria-current="page">Shoes</a></li>
</ol>
</nav>
Checklist
- Use
<nav>orrole="navigation" - Set
aria-label="Breadcrumb" - Set
aria-current="page"on current page - Handle separators via CSS (not read by screen readers)
Button
Widget that triggers actions such as form submission, opening dialogs, etc.
Button Types
- Standard Button: Default command button
- Toggle Button: Express pressed/unpressed state with
aria-pressed - Menu Button: Set
aria-haspopup="menu"
Keyboard Interaction
| Key | Action |
|---|---|
Enter |
Activate button |
Space |
Activate button |
ARIA Attributes
<!-- Standard button -->
<button>Save</button>
<!-- Custom button -->
<div role="button" tabindex="0" aria-label="Save">Save</div>
<!-- Toggle button -->
<button aria-pressed="false">Mute</button>
<!-- Disabled button -->
<button aria-disabled="true">Submit</button>
Checklist
- Prefer native
<button>element - Custom elements:
role="button"+tabindex="0" - Handle both
EnterandSpace - Toggle buttons: manage
aria-pressedstate - Disabled: use
aria-disabled="true"(instead of disabled attribute) - Dialog-opening buttons: add ellipsis "Save..." convention
Carousel
Content presentation that displays slides sequentially.
Keyboard Interaction
| Key | Action |
|---|---|
Tab |
Move between interactive elements in carousel |
| Focus entry | Stop auto-rotation |
ARIA Attributes
<div role="region" aria-roledescription="carousel" aria-label="Featured Products">
<!-- Rotation controls -->
<button aria-label="Pause slide rotation">Pause</button>
<button aria-label="Previous slide">Previous</button>
<button aria-label="Next slide">Next</button>
<!-- Slide container -->
<div aria-live="polite">
<div role="group" aria-roledescription="slide" aria-label="1 of 5">Slide content</div>
</div>
</div>
Checklist
- Container:
aria-roledescription="carousel" - Each slide:
aria-roledescription="slide" - Provide slide position info (e.g., "1 of 5")
- Auto-rotation requires stop/resume button
- Stop auto-rotation on focus entry
- Stop auto-rotation on mouse hover
- Set
aria-live(auto-rotation:off, manual:polite)
Checkbox
Supports checked/unchecked or partial checked states.
Keyboard Interaction
| Key | Action |
|---|---|
Space |
Toggle check state |
ARIA Attributes
<!-- Native checkbox -->
<input type="checkbox" id="agree" />
<label for="agree">I agree</label>
<!-- Custom checkbox -->
<div role="checkbox" tabindex="0" aria-checked="false" aria-labelledby="label-id"></div>
<span id="label-id">I agree</span>
<!-- Tri-state (mixed) -->
<div role="checkbox" aria-checked="mixed"></div>
Checklist
- Prefer native
<input type="checkbox"> - Custom:
role="checkbox"+tabindex="0" - Manage
aria-checkedstate (true,false,mixed) - Handle
Spacekey for toggle - Group with
role="group"+aria-labelledby
Combobox
Input widget with autocomplete functionality.
Keyboard Interaction (Popup Closed)
| Key | Action |
|---|---|
↓ |
Open popup, move to first option |
↑ |
Open popup, move to last option |
Escape |
Close popup |
Enter |
Accept autocomplete suggestion |
Keyboard Interaction (Popup Open)
| Key | Action |
|---|---|
↓ / ↑ |
Move between options |
Enter |
Select option, close popup |
Escape |
Close popup, return to input |
Home / End |
Move to first/last option |
ARIA Attributes
<label for="city">City</label>
<input
type="text"
id="city"
role="combobox"
aria-expanded="false"
aria-controls="city-listbox"
aria-autocomplete="list"
aria-activedescendant="option-1"
/>
<ul id="city-listbox" role="listbox">
<li id="option-1" role="option" aria-selected="true">Seoul</li>
<li id="option-2" role="option">Busan</li>
</ul>
Checklist
- Input element:
role="combobox" - Manage
aria-expandedstate - Connect popup with
aria-controls - Set
aria-autocomplete(none,list,both) - Reference current option with
aria-activedescendant - Popup: appropriate role (
listbox,grid,tree,dialog) - DOM focus stays on combobox
Dialog (Modal)
Modal window overlaid on main window. Interaction with content outside dialog is blocked.
Keyboard Interaction
| Key | Action |
|---|---|
Tab |
Cycle focus within dialog (last→first) |
Shift+Tab |
Reverse cycle (first→last) |
Escape |
Close dialog |
Initial Focus Placement Rules
- Structured content (lists, tables): Add
tabindex="-1"to start point - Large content: Focus on title or first paragraph
- Destructive actions (delete, etc.): Focus on safest button (Cancel)
ARIA Attributes
<div role="dialog" aria-modal="true" aria-labelledby="dialog-title" aria-describedby="dialog-desc">
<h2 id="dialog-title">Delete File</h2>
<p id="dialog-desc">Are you sure you want to delete this file?</p>
<button>Delete</button>
<button>Cancel</button>
</div>
Checklist
- Set
role="dialog" - Set
aria-modal="true" - Connect title with
aria-labelledby - Connect description with
aria-describedby(optional) - Move focus to internal element on open
- Return focus to trigger element on close
- Implement focus trap (required)
- Set
aria-hidden="true"orinerton background content
Disclosure
Widget that collapses or expands content.
Keyboard Interaction
| Key | Action |
|---|---|
Enter |
Toggle content show/hide |
Space |
Toggle content show/hide |
ARIA Attributes
<button aria-expanded="false" aria-controls="content-id">Show more</button>
<div id="content-id" hidden>Hidden content</div>
Checklist
- Toggle element has
buttonrole - Manage
aria-expandedstate - Connect content with
aria-controls(optional) - Icons: right arrow (▶) when collapsed, down arrow (▼) when expanded
Feed
Section that automatically loads new content as user scrolls.
Keyboard Interaction
| Key | Action |
|---|---|
Page Down |
Move focus to next article |
Page Up |
Move focus to previous article |
Ctrl+End |
Move to element after feed |
Ctrl+Home |
Move to element before feed |
ARIA Attributes
<div role="feed" aria-labelledby="feed-title" aria-busy="false">
<h2 id="feed-title">Latest News</h2>
<article aria-posinset="1" aria-setsize="-1" aria-labelledby="article-1-title" aria-describedby="article-1-content">
<h3 id="article-1-title">Article Title</h3>
<p id="article-1-content">Article content</p>
</article>
</div>
Checklist
- Container:
role="feed" - Each article:
role="article"or<article> -
aria-posinsetfor article position -
aria-setsizefor total articles (-1 if unknown) - Set
aria-busy="true"during DOM update - Must set
aria-busy="false"when complete
Grid
Interactive tabular data navigable with arrow keys.
Keyboard Interaction
| Key | Action |
|---|---|
→ / ← / ↑ / ↓ |
Move focus between cells |
Home / End |
Move to first/last cell in row |
Ctrl+Home / Ctrl+End |
Move to first/last cell in grid |
Page Up / Page Down |
Scroll by row |
Enter / F2 |
Enter cell edit mode |
Escape |
Restore grid navigation |
ARIA Attributes
<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" tabindex="0">John Doe</div>
<div role="gridcell">30</div>
</div>
</div>
Checklist
- Container:
role="grid" - Rows:
role="row" - Headers:
columnheader/rowheaderroles - Data cells:
role="gridcell" - Manage
tabindexon focusable cells -
aria-selectedfor selection state -
aria-sortfor sort state
Link
Interactive reference to a resource.
Keyboard Interaction
| Key | Action |
|---|---|
Enter |
Execute link |
ARIA Attributes
<!-- Native link (recommended) -->
<a href="/page">Go to page</a>
<!-- Custom link -->
<span role="link" tabindex="0" onclick="navigate()" onkeydown="handleKeyDown(event)"> Go to page </span>
Checklist
- Strongly prefer native
<a>element - Custom:
role="link"+tabindex="0" - Handle
Enterkey (required) - Implement same behavior as click
Listbox
List where user can select one or more options.
Keyboard Interaction (Single Select)
| Key | Action |
|---|---|
↓ / ↑ |
Move focus |
Home / End |
Move to first/last option |
| Type character | Search for item starting with that character |
Keyboard Interaction (Multi Select)
| Key | Action |
|---|---|
Space |
Toggle focused option selection |
Shift+↓ / Shift+↑ |
Move focus + toggle selection |
Ctrl+A |
Select/deselect all |
ARIA Attributes
<div role="listbox" aria-labelledby="label-id" aria-multiselectable="true">
<div role="option" aria-selected="true">Option 1</div>
<div role="option" aria-selected="false">Option 2</div>
</div>
Checklist
- Container:
role="listbox" - Each option:
role="option" -
aria-selectedfor selection state - Multi-select:
aria-multiselectable="true" - Horizontal layout:
aria-orientation="horizontal"
Menu and Menubar
Widget offering list of actions or functions.
Keyboard Interaction
| Key | Action |
|---|---|
Enter / Space |
Activate item |
↓ / ↑ |
Move in vertical menu |
→ / ← |
Move in horizontal menubar / Open/close submenu |
Escape |
Close menu |
Home / End |
Move to first/last item |
| Type character | Move to matching label item |
ARIA Attributes
<div role="menubar" aria-label="Edit">
<div role="menuitem" aria-haspopup="menu" aria-expanded="false">
File
<div role="menu">
<div role="menuitem">New File</div>
<div role="menuitem">Open</div>
<div role="menuitemcheckbox" aria-checked="true">Auto Save</div>
</div>
</div>
</div>
Checklist
- Container:
role="menu"orrole="menubar" - Items:
menuitem/menuitemcheckbox/menuitemradioroles - Submenu presence:
aria-haspopup="menu" - Manage submenu open state with
aria-expanded - Checkbox/radio items: manage
aria-checked - Disabled items:
aria-disabled="true"
Menu Button
Button that opens a menu.
Keyboard Interaction
| Key | Action |
|---|---|
Enter / Space |
Open menu, focus first item |
↓ |
(Optional) Open menu, focus first item |
↑ |
(Optional) Open menu, focus last item |
ARIA Attributes
<button aria-haspopup="menu" aria-expanded="false" aria-controls="menu-id">Options</button>
<div id="menu-id" role="menu">
<div role="menuitem">Item 1</div>
<div role="menuitem">Item 2</div>
</div>
Checklist
- Button:
aria-haspopup="menu"or"true" - Manage
aria-expandedstate - Connect menu with
aria-controls(optional) - Menu:
role="menu"
Meter
Graphical display of numeric value within a defined range.
Warning: Use
progressbarrole for progress indication
ARIA Attributes
<div role="meter" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100" aria-label="Battery level">75%</div>
<!-- With additional description -->
<div
role="meter"
aria-valuenow="50"
aria-valuemin="0"
aria-valuemax="100"
aria-valuetext="50% (6 hours) remaining"
aria-label="Battery"
></div>
Checklist
- Set
role="meter" -
aria-valuenowcurrent value -
aria-valueminminimum value -
aria-valuemaxmaximum value -
aria-valuetextuser-friendly value (optional) -
aria-labeloraria-labelledbyfor label
Radio Group
Set of radio buttons where only one can be selected at a time.
Keyboard Interaction
| Key | Action |
|---|---|
Tab |
Enter group (selected button or first button) |
Space |
Select current focused button |
→ / ↓ |
Move to next button + select (cycles) |
← / ↑ |
Move to previous button + select (cycles) |
ARIA Attributes
<div role="radiogroup" aria-labelledby="group-label">
<span id="group-label">Shipping Method</span>
<div role="radio" aria-checked="true" tabindex="0">Standard Shipping</div>
<div role="radio" aria-checked="false" tabindex="-1">Express Shipping</div>
</div>
Checklist
- Container:
role="radiogroup" - Each button:
role="radio" - Manage
aria-checkedstate - Selected button:
tabindex="0", others:tabindex="-1" - Arrow key selection moves focus too
Slider
Input widget for selecting value within a given range.
Keyboard Interaction
| Key | Action |
|---|---|
→ / ↑ |
Increase value |
← / ↓ |
Decrease value |
Home |
Set to minimum |
End |
Set to maximum |
Page Up |
(Optional) Increase by large step |
Page Down |
(Optional) Decrease by large step |
ARIA Attributes
<div role="slider" tabindex="0" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100" aria-label="Volume"></div>
<!-- With user-friendly value -->
<div
role="slider"
aria-valuenow="1"
aria-valuemin="0"
aria-valuemax="6"
aria-valuetext="Monday"
aria-label="Day selection"
></div>
Checklist
- Set
role="slider" - Set
tabindex="0" -
aria-valuenowcurrent value -
aria-valueminminimum value -
aria-valuemaxmaximum value -
aria-valuetextuser-friendly value (optional) - Vertical slider:
aria-orientation="vertical"
Spinbutton
Input widget for selecting value from discrete value range.
Keyboard Interaction
| Key | Action |
|---|---|
↑ |
Increase value |
↓ |
Decrease value |
Home |
Set to minimum |
End |
Set to maximum |
Page Up |
(Optional) Increase by large step |
Page Down |
(Optional) Decrease by large step |
ARIA Attributes
<div role="spinbutton" tabindex="0" aria-valuenow="5" aria-valuemin="0" aria-valuemax="59" aria-label="Minutes">
<button aria-label="Increase">+</button>
<span>05</span>
<button aria-label="Decrease">-</button>
</div>
Checklist
- Set
role="spinbutton" -
aria-valuenowcurrent value -
aria-valueminminimum value -
aria-valuemaxmaximum value -
aria-valuetextuser-friendly value (optional) - Out of range:
aria-invalid="true"
Switch
Widget for selecting between on/off states.
Keyboard Interaction
| Key | Action |
|---|---|
Space |
Toggle state |
Enter |
(Optional) Toggle state |
ARIA Attributes
<button role="switch" aria-checked="false">Notifications</button>
<!-- HTML checkbox based -->
<input type="checkbox" role="switch" aria-checked="true" />
Checklist
- Set
role="switch" - Manage
aria-checkedstate (true/false) - Label should NOT change on state change
- Group with
role="group"+aria-labelledby
Table
Static table structure (no interactive features).
For interactive tables, use Grid pattern
ARIA Attributes
<table role="table" aria-labelledby="table-title">
<caption id="table-title">
User List
</caption>
<thead>
<tr role="row">
<th role="columnheader" aria-sort="ascending">Name</th>
<th role="columnheader">Email</th>
</tr>
</thead>
<tbody>
<tr role="row">
<td role="cell">John Doe</td>
<td role="cell">john@example.com</td>
</tr>
</tbody>
</table>
Checklist
- Prefer native
<table>element - Custom:
role="table"+row+cellstructure - Headers:
columnheader/rowheaderroles -
aria-labeloraria-labelledbyfor table label - Sorted columns: set
aria-sort
Tabs
Layered content sections displaying one panel at a time.
Keyboard Interaction
| Key | Action |
|---|---|
Tab |
Enter tab list (active tab) |
→ / ← |
Move to next/previous tab (cycles) |
Space / Enter |
(Manual activation) Activate tab |
Home / End |
Move to first/last tab |
Delete |
(Optional) Delete tab |
ARIA Attributes
<div role="tablist" aria-label="Content tabs">
<button role="tab" aria-selected="true" aria-controls="panel-1" tabindex="0">Tab 1</button>
<button role="tab" aria-selected="false" aria-controls="panel-2" tabindex="-1">Tab 2</button>
</div>
<div id="panel-1" role="tabpanel" aria-labelledby="tab-1" tabindex="0">Panel 1 content</div>
<div id="panel-2" role="tabpanel" aria-labelledby="tab-2" hidden>Panel 2 content</div>
Checklist
- Tab container:
role="tablist" - Each tab:
role="tab" - Content area:
role="tabpanel" - Manage
aria-selectedstate - Connect tab-panel with
aria-controls - Active tab:
tabindex="0", others:tabindex="-1" - Vertical tabs:
aria-orientation="vertical"
Tooltip
Popup displaying information when element receives focus or hover.
Warning: This pattern is currently under W3C development
Keyboard Interaction
| Key | Action |
|---|---|
Escape |
Close tooltip |
ARIA Attributes
<button aria-describedby="tooltip-1">Save</button>
<div id="tooltip-1" role="tooltip">Save your changes (Ctrl+S)</div>
Checklist
- Tooltip:
role="tooltip" - Trigger element:
aria-describedbyreferencing tooltip - Focus stays on trigger element
- Display after slight delay
- Close with
Escapekey - Auto-close on focus loss
- Stay open while cursor on trigger/tooltip
Tree View
Widget presenting hierarchical list.
Keyboard Interaction
| Key | Action |
|---|---|
Enter |
Activate node (default action) |
→ |
Open closed node / Move to child |
← |
Close open node / Move to parent |
↓ / ↑ |
Move to next/previous focusable node |
Home / End |
Move to first/last node |
| Type character | Move to node starting with that character |
Keyboard Interaction (Multi Select)
| Key | Action |
|---|---|
Space |
Toggle focused node selection |
Shift+↓ / Shift+↑ |
Toggle selection while moving |
Ctrl+A |
Select/deselect all |
ARIA Attributes
<ul role="tree" aria-labelledby="tree-label" aria-multiselectable="false">
<li role="treeitem" aria-expanded="true" aria-selected="false">
Folder 1
<ul role="group">
<li role="treeitem" aria-selected="true">File 1</li>
<li role="treeitem" aria-selected="false">File 2</li>
</ul>
</li>
</ul>
Checklist
- Container:
role="tree" - Each node:
role="treeitem" - Child container:
role="group" - Manage
aria-expandedstate (open/close) -
aria-selectedfor selection state - Multi-select:
aria-multiselectable="true" -
aria-level,aria-setsize,aria-posinsetfor hierarchy info - Visually distinguish focus and selection states
Common Patterns
Focus Management
// Focus trap (for modal dialogs)
function trapFocus(element: HTMLElement) {
const focusableElements = element.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const first = focusableElements[0] as HTMLElement;
const last = focusableElements[focusableElements.length - 1] as HTMLElement;
element.addEventListener('keydown', (e) => {
if (e.key !== 'Tab') return;
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
});
}
Roving Tabindex
Used in tab lists, radio groups, etc.:
// Only active element has tabindex="0", others have tabindex="-1"
function setRovingTabindex(container: HTMLElement, activeSelector: string) {
const items = container.querySelectorAll('[role="tab"], [role="radio"]');
items.forEach((item) => {
(item as HTMLElement).tabIndex = item.matches(activeSelector) ? 0 : -1;
});
}
Live Regions
Announce dynamic content changes:
<!-- Immediate announcement (urgent) -->
<div aria-live="assertive" aria-atomic="true">Error: This field is required.</div>
<!-- Next opportunity announcement (normal) -->
<div aria-live="polite">3 search results found</div>