aria-authoring-patterns
Reference for the W3C ARIA Authoring Practices Guide (APG) - covers the 31 canonical interactive-widget patterns (Combobox, Dialog, Menu, Tabs, Tree, etc.), their required ARIA roles and states, the keyboard-interaction model per pattern, and the canonical-violations to watch for. Use when authoring a custom interactive widget that doesn't have a native HTML equivalent, or when reviewing one for ARIA correctness.
Install with skills.sh (any agent)
npx skills add testland/qa --skill aria-authoring-patternsaria-authoring-patterns
Overview
The W3C ARIA Authoring Practices Guide (APG) is the canonical reference for how to build accessible custom widgets - patterns where native HTML doesn't have a single matching element (apg-patterns (opens in new window)).
The APG documents 31 patterns; each one specifies:
When to use
The first rule of ARIA
The APG (and WAI more broadly) consistently emphasizes:
No ARIA is better than bad ARIA. Native HTML elements with built-in semantics are preferred over custom widgets whenever possible.
In practice:
| Native element | Don't replace with |
|---|---|
<button> | <div role="button"> - same semantics, more code, easier to break. |
<input type="checkbox"> | Custom checkbox unless visual constraints demand it. |
<a href> | <div onclick="navigate(...)">. |
<select> | Custom listbox UNLESS multi-select with rich content per option. |
<input type="radio"> | Custom radio group. |
When the visual design demands a custom widget, follow the matching APG pattern exactly.
Canonical patterns (APG)
The 31 patterns documented per apg-patterns (opens in new window):
Form controls
| Pattern | When to use | Native fallback |
|---|---|---|
| Button | Trigger an action. | <button> - almost always. |
| Checkbox | Two-state binary; or three-state (mixed). | <input type="checkbox">. |
| Combobox | Searchable / filterable select with autocomplete. | <select> for plain selection only. |
| Disclosure | Show/hide content; one-way reveal. | <details> / <summary>. |
| Listbox | Multi-select OR rich-content single-select. | <select> for plain. |
| Menu / Menubar | Application menus (File / Edit / View bar). | (No native; APG required.) |
| Menu Button | Button that opens a menu. | (No native; APG required.) |
| Radio Group | Mutually-exclusive option set. | <input type="radio" name="x">. |
| Slider | Single-value within a range. | <input type="range">. |
| Slider (Multi-Thumb) | Range selection. | (No native; APG required.) |
| Spinbutton | Numeric input with increment/decrement controls. | <input type="number">. |
| Switch | On/off toggle, semantically distinct from checkbox. | (No native; APG required.) |
Containers / navigation
| Pattern | When to use |
|---|---|
| Accordion | Vertically-stacked list of headers; each expands. |
| Breadcrumb | Hierarchical-location indicator. |
| Carousel | Cycle through groups of equivalent content. |
| Dialog (Modal) | Overlay requiring user interaction. (See wcag-keyboard-navigation references/focus-trap.md.) |
| Alert Dialog | Modal that interrupts to convey urgency. |
| Tabs | Switch between sibling content sections. |
| Toolbar | Group of controls (buttons, dropdowns). |
| Tree View | Hierarchical navigation. |
| Treegrid | Hierarchical data grid (table rows that expand). |
| Window Splitter | Resize between two panes. |
Feedback / indicators
| Pattern | When to use |
|---|---|
| Alert | Important programmatically-determined message. |
| Feed | Dynamic stream of articles. |
| Grid | 2D widget for navigating tabular UI elements. |
| Meter | Scalar measurement within a known range. |
| Tooltip | Brief contextual hint on hover/focus. |
Content
| Pattern | When to use |
|---|---|
| Landmarks | Navigation regions (<nav>, <main>, etc.). |
| Link | Navigation to a different resource. |
| Table | Tabular data (use <table>). |
Per-pattern essentials
The APG's per-pattern documentation is detailed; for each pattern this skill highlights the load-bearing essentials.
Combobox
<label for="combo">Choose a fruit</label>
<input
id="combo"
role="combobox"
aria-expanded="false"
aria-controls="combo-listbox"
aria-autocomplete="list"
aria-activedescendant=""
/>
<ul id="combo-listbox" role="listbox" hidden>
<li id="opt-1" role="option" aria-selected="false">Apple</li>
<li id="opt-2" role="option" aria-selected="false">Banana</li>
</ul>| Required ARIA | Why |
|---|---|
role="combobox" | Identifies the input as a combobox. |
aria-expanded | Tracks open/closed state. |
aria-controls | Links to the listbox ID. |
aria-autocomplete | none / inline / list / both. |
aria-activedescendant | Tracks the focused option without moving DOM focus. |
Tabs
<div role="tablist" aria-label="Settings sections">
<button role="tab" id="tab-1" aria-selected="true" aria-controls="panel-1" tabindex="0">General</button>
<button role="tab" id="tab-2" aria-selected="false" aria-controls="panel-2" tabindex="-1">Privacy</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">...</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>...</div>| Keyboard | Behavior |
|---|---|
| Tab | One tabstop for the whole tablist (only the active tab is tabindex="0"). |
| Left/Right arrow | Navigate between tabs. |
| Home / End | First / last tab. |
| Enter / Space | Activate (or auto on focus per implementation). |
Disclosure (Show/Hide)
<button aria-expanded="false" aria-controls="disclosure-1">More details</button>
<div id="disclosure-1" hidden>... content ...</div>The simplest pattern; canonical native equivalent is <details> / <summary> - use that unless the visual design demands the button form.
Tooltip (per SC 1.4.13)
<button aria-describedby="tt-1">Save</button>
<div id="tt-1" role="tooltip" hidden>Save the current document</div>| Behavior |
|---|
| Show on hover OR focus. |
| Dismissable via Esc (per SC 1.4.13). |
| Hoverable - user can move pointer to tooltip text. |
| Persistent until pointer / focus leaves OR Esc dismisses. |
(See wcag-color-contrast SC 1.4.13 for the three conditions.)
ARIA states reference
The most-used aria-* states across patterns:
| State | Use |
|---|---|
aria-expanded | Disclosure / Accordion / Combobox / Menu Button. |
aria-selected | Listbox / Tab. |
aria-checked | Checkbox / Radio. |
aria-pressed | Toggle button. |
aria-current | Current item in a set (page in pagination, item in breadcrumb). |
aria-controls | Element that this trigger controls. |
aria-labelledby | Reference to the labelling element. |
aria-describedby | Reference to a description element (tooltip, hint). |
aria-live | Live region; polite / assertive / off. |
aria-invalid | Form field validation state. |
aria-required | Form field required (with native required for forms). |
aria-disabled | Functionally disabled but in tab order (vs. disabled attribute which removes from tab order). |
aria-modal | Dialog modal flag; see wcag-keyboard-navigation references/focus-trap.md. |
Common ARIA failures
| Failure | Why | Fix |
|---|---|---|
<div role="button"> without keyboard handlers | Custom button needs tabindex="0" AND Enter/Space handlers. | Use <button> OR add both. |
aria-label on a <div> that has visible text label | Redundant; aria-label overrides the visible text for screen readers (becomes confusing). | Use aria-labelledby referencing the visible text element. |
aria-hidden="true" on a focusable element | Element is hidden from screen readers but still in tab order - broken state. | If you want to hide, also remove from tab order. |
Forgetting role="tab" / role="tabpanel" pairs | Screen reader doesn't announce the relationship. | Pair every tab with a panel via aria-controls / aria-labelledby. |
Hand-rolled combobox without aria-activedescendant | Arrow keys don't announce options. | Implement aria-activedescendant or move actual DOM focus. |
role="presentation" on a meaningful container | Removes semantics; sometimes used to "fix" a layout issue but breaks structure. | Don't override structure to fix layout; fix layout. |
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Inventing a custom pattern not in the APG | No screen-reader convention; users can't form expectations. | Match an APG pattern exactly OR fall back to native HTML. |
Mixing native and ARIA semantics (<button role="link">) | Confusing - element behaves like a button but says it's a link. | One or the other; native is preferred. |
tabindex="3" to control sequence | Positive tabindex creates fragile order. | DOM order + tabindex="0" for custom focusables. |
Adding aria-label to every element "for accessibility" | If the element has a visible label, aria-label overrides it. Many elements don't need a label at all. | Only add aria-label when there's no visible text label AND the element is interactive. |
References
Related skills
a11y-violation-gate
Builds a CI gate that fails the build on **new** WCAG / a11y violations introduced by a PR while grandfathering pre-existing violations on a per-rule / per-page baseline. Aggregates verdicts from axe-core / pa11y / Lighthouse a11y / WAVE / IBM Equal Access scans. Use when a project has accumulated a11y debt and a strict "zero violations" gate would block every PR - the ratchet pattern lets the team ship while preventing regressions.
axe-a11y
Automated accessibility scanning across the five engines - axe-core (primary), pa11y, Lighthouse a11y, WAVE, and IBM Equal Access. Authors and runs axe-core scans via the `axe.run()` JavaScript API or the @axe-core/playwright / @axe-core/cli wrappers, parses `violations[]` into per-rule severity, configures rule disable / disable-by-tag patterns, and emits CI-gateable output; references/ carry the pa11y CLI (htmlcs + axe runners), Lighthouse CI `categories:accessibility` assertions, the WAVE API / overlay, and IBM Equal Access (Section 508) with their verified CLI / API / config. Use for any automated a11y scanner setup - axe-core for JS/TS UI test suites on every PR, and the references for CLI-only, Lighthouse-pipeline, WebAIM-branded, or Section 508 scanning.
screen-reader-test-author
Builds the full manual-accessibility artifact surface: step-by-step screen-reader test scripts for NVDA (Windows), JAWS (Windows), VoiceOver (macOS / iOS), or TalkBack (Android) with per-step keystroke + expected announcement; per-archetype WCAG 2.2 checklists (references/wcag-checklist.md); per-widget keystroke matrices pairing expected NVDA and VoiceOver announcements with the WCAG SC each row verifies (references/widget-matrix.md); and a guided NVDA / VoiceOver session protocol that merges script + checklist into a signed pass/fail session report. Use when authoring an accessibility-acceptance test, checklist, or widget matrix the team will run before sign-off, when scripting a manual a11y audit, OR when walking a tester through a guided screen-reader session.
wcag-color-contrast
Reference for WCAG 2.2 color-contrast conformance - covers SC 1.4.3 Contrast (Minimum, AA), 1.4.6 Contrast (Enhanced, AAA), 1.4.11 Non-text Contrast (AA), and 1.4.13 Content on Hover or Focus (AA) - with the canonical contrast ratios (4.5:1 normal text, 3:1 large text and UI components), measurement formula references, and bulk design-token checking patterns. Use when designing a color palette, reviewing a component for accessibility, or auditing existing CSS for contrast violations.
wcag-compliance-reporter
Builds a per-page WCAG 2.2 compliance score report by aggregating output from one or more accessibility scanners (axe-core / pa11y / lighthouse / WAVE / IBM Equal Access), pivoting violations by Success Criterion (1.4.3 contrast, 2.4.7 focus visible, etc.), grouping by conformance level (A / AA / AAA), reporting per-page coverage gaps explicitly (the "this page wasn't scanned" failure mode), and emitting both an executive summary and a per-page drill-down. Use after a multi-page accessibility scan - pa11y-ci, axe across a sitemap, lighthouse-batch - when the team needs a shareable conformance report rather than a per-page tool dump.
wcag-keyboard-navigation
Reference catalog for WCAG 2.2 keyboard-navigation conformance - covers SC 2.1.1 (Keyboard), 2.1.2 (No Keyboard Trap), 2.1.4 (Character Key Shortcuts), 2.4.3 (Focus Order), 2.4.7 (Focus Visible), 2.4.11/2.4.12 (Focus Not Obscured) - with conformance levels (A/AA), test scripts, and per-criterion failure patterns, plus the modal focus-trap / focus-management pattern (focus-on-open, Tab-cycle, inert, Escape-closes, restore-to-trigger, native `<dialog>`) in references/. Use when authoring or reviewing keyboard-only interaction support, or a modal / drawer / popover's focus management.