一键导入
aria-apg-patterns
Accessibility patterns reference based on W3C WAI-ARIA Authoring Practices Guide for implementing accessible web UI components.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Accessibility patterns reference based on W3C WAI-ARIA Authoring Practices Guide for implementing accessible web UI components.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | aria-apg-patterns |
| description | Accessibility patterns reference based on W3C WAI-ARIA Authoring Practices Guide for implementing accessible web UI components. |
Accessibility patterns reference based on W3C WAI-ARIA Authoring Practices Guide
This document provides ARIA pattern guidelines for implementing accessible web UI components.
| 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 |
Vertically stacked set of interactive headings. Each section can be expanded or collapsed.
| 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 |
<!-- 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>
button rolearia-expanded statearia-controlsrole="region" (when 6 or fewer panels expanded)aria-labelledby referencing headerDisplays important message without interrupting user's current task. Does not move focus.
<div role="alert">Important message content</div>
role="alert"Modal dialog that interrupts user to deliver important message.
| Key | Action |
|---|---|
Tab | Cycle focus within dialog |
Shift+Tab | Reverse cycle focus |
Escape | Close dialog |
<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>
role="alertdialog"aria-modal="true"aria-labelledbyDisplays parent page links of current page in hierarchical order.
<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>
<nav> or role="navigation"aria-label="Breadcrumb"aria-current="page" on current pageWidget that triggers actions such as form submission, opening dialogs, etc.
aria-pressedaria-haspopup="menu"| Key | Action |
|---|---|
Enter | Activate button |
Space | Activate button |
<!-- 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>
<button> elementrole="button" + tabindex="0"Enter and Spacearia-pressed statearia-disabled="true" (instead of disabled attribute)Content presentation that displays slides sequentially.
| Key | Action |
|---|---|
Tab | Move between interactive elements in carousel |
| Focus entry | Stop auto-rotation |
<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>
aria-roledescription="carousel"aria-roledescription="slide"aria-live (auto-rotation: off, manual: polite)Supports checked/unchecked or partial checked states.
| Key | Action |
|---|---|
Space | Toggle check state |
<!-- 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>
<input type="checkbox">role="checkbox" + tabindex="0"aria-checked state (true, false, mixed)Space key for togglerole="group" + aria-labelledbyInput widget with autocomplete functionality.
| Key | Action |
|---|---|
↓ | Open popup, move to first option |
↑ | Open popup, move to last option |
Escape | Close popup |
Enter | Accept autocomplete suggestion |
| Key | Action |
|---|---|
↓ / ↑ | Move between options |
Enter | Select option, close popup |
Escape | Close popup, return to input |
Home / End | Move to first/last option |
<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>
role="combobox"aria-expanded statearia-controlsaria-autocomplete (none, list, both)aria-activedescendantlistbox, grid, tree, dialog)Modal window overlaid on main window. Interaction with content outside dialog is blocked.
| Key | Action |
|---|---|
Tab | Cycle focus within dialog (last→first) |
Shift+Tab | Reverse cycle (first→last) |
Escape | Close dialog |
tabindex="-1" to start point<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>
role="dialog"aria-modal="true"aria-labelledbyaria-describedby (optional)aria-hidden="true" or inert on background contentWidget that collapses or expands content.
| Key | Action |
|---|---|
Enter | Toggle content show/hide |
Space | Toggle content show/hide |
<button aria-expanded="false" aria-controls="content-id">Show more</button>
<div id="content-id" hidden>Hidden content</div>
button rolearia-expanded statearia-controls (optional)Section that automatically loads new content as user scrolls.
| 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 |
<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>
role="feed"role="article" or <article>aria-posinset for article positionaria-setsize for total articles (-1 if unknown)aria-busy="true" during DOM updatearia-busy="false" when completeInteractive tabular data navigable with arrow keys.
| 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 |
<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>
role="grid"role="row"columnheader / rowheader rolesrole="gridcell"tabindex on focusable cellsaria-selected for selection statearia-sort for sort stateInteractive reference to a resource.
| Key | Action |
|---|---|
Enter | Execute link |
<!-- 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>
<a> elementrole="link" + tabindex="0"Enter key (required)List where user can select one or more options.
| Key | Action |
|---|---|
↓ / ↑ | Move focus |
Home / End | Move to first/last option |
| Type character | Search for item starting with that character |
| Key | Action |
|---|---|
Space | Toggle focused option selection |
Shift+↓ / Shift+↑ | Move focus + toggle selection |
Ctrl+A | Select/deselect all |
<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>
role="listbox"role="option"aria-selected for selection statearia-multiselectable="true"aria-orientation="horizontal"Widget offering list of actions or functions.
| 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 |
<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>
role="menu" or role="menubar"menuitem / menuitemcheckbox / menuitemradio rolesaria-haspopup="menu"aria-expandedaria-checkedaria-disabled="true"Button that opens a menu.
| Key | Action |
|---|---|
Enter / Space | Open menu, focus first item |
↓ | (Optional) Open menu, focus first item |
↑ | (Optional) Open menu, focus last item |
<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>
aria-haspopup="menu" or "true"aria-expanded statearia-controls (optional)role="menu"Graphical display of numeric value within a defined range.
Warning: Use
progressbarrole for progress indication
<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>
role="meter"aria-valuenow current valuearia-valuemin minimum valuearia-valuemax maximum valuearia-valuetext user-friendly value (optional)aria-label or aria-labelledby for labelSet of radio buttons where only one can be selected at a time.
| 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) |
<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>
role="radiogroup"role="radio"aria-checked statetabindex="0", others: tabindex="-1"Input widget for selecting value within a given range.
| 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 |
<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>
role="slider"tabindex="0"aria-valuenow current valuearia-valuemin minimum valuearia-valuemax maximum valuearia-valuetext user-friendly value (optional)aria-orientation="vertical"Input widget for selecting value from discrete value range.
| 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 |
<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>
role="spinbutton"aria-valuenow current valuearia-valuemin minimum valuearia-valuemax maximum valuearia-valuetext user-friendly value (optional)aria-invalid="true"Widget for selecting between on/off states.
| Key | Action |
|---|---|
Space | Toggle state |
Enter | (Optional) Toggle state |
<button role="switch" aria-checked="false">Notifications</button>
<!-- HTML checkbox based -->
<input type="checkbox" role="switch" aria-checked="true" />
role="switch"aria-checked state (true / false)role="group" + aria-labelledbyStatic table structure (no interactive features).
For interactive tables, use Grid pattern
<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>
<table> elementrole="table" + row + cell structurecolumnheader / rowheader rolesaria-label or aria-labelledby for table labelaria-sortLayered content sections displaying one panel at a time.
| 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 |
<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>
role="tablist"role="tab"role="tabpanel"aria-selected statearia-controlstabindex="0", others: tabindex="-1"aria-orientation="vertical"Popup displaying information when element receives focus or hover.
Warning: This pattern is currently under W3C development
| Key | Action |
|---|---|
Escape | Close tooltip |
<button aria-describedby="tooltip-1">Save</button>
<div id="tooltip-1" role="tooltip">Save your changes (Ctrl+S)</div>
role="tooltip"aria-describedby referencing tooltipEscape keyWidget presenting hierarchical list.
| 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 |
| Key | Action |
|---|---|
Space | Toggle focused node selection |
Shift+↓ / Shift+↑ | Toggle selection while moving |
Ctrl+A | Select/deselect all |
<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>
role="tree"role="treeitem"role="group"aria-expanded state (open/close)aria-selected for selection statearia-multiselectable="true"aria-level, aria-setsize, aria-posinset for hierarchy info// 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();
}
});
}
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;
});
}
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>