This skill defines the W3C WAI-ARIA 1.2 surface and the WAI Authoring Practices Guide (APG) pattern set required to ship accessible custom widgets. ARIA 1.2 is a stable W3C Recommendation; APG patterns are the normative-in-practice author guidance for combining roles, states, and keyboard models. This skill builds on [[frontend-syntax-html5-semantic]] (native elements first) and [[frontend-core-web-standards-baseline]] (browser AT support).
Sources : W3C: WAI-ARIA 1.2 (verified 2026-05-19), W3C: ARIA in HTML (verified 2026-05-19), W3C WAI: APG patterns (verified 2026-05-19), per-pattern URLs cited in methods.md.
Quick Reference
First rule of ARIA (verbatim, normative)
W3C: WAI-ARIA 1.2 (verified 2026-05-19) : "WAI-ARIA is intended to be used as a supplement for native language semantics, not a replacement. When the host language provides a feature that provides equivalent accessibility to the WAI-ARIA feature, use the host language feature."
Three operational consequences :
- NEVER add
roleto an element whose implicit role already satisfies the requirement (<nav>,<button>,<main>,<dialog>,<details>). - NEVER add
roleto an element if doing so contradicts the native role (<a role="button" href="...">strips link semantics;<button role="heading">is invalid). W3C: ARIA in HTML (verified 2026-05-19) : "Authors MUST NOT use the ARIA role and aria-* attributes in a manner that conflicts with the semantics." - ALWAYS prefer a composition of native elements over an ARIA pattern when both deliver the same accessibility (
<details><summary>over a Disclosure pattern;<dialog>showModal()over a customrole="dialog").
When to reach for ARIA (decision tree)
What does the widget need to be?
A single semantic element exists in HTML (button, link, nav, dialog,
details, input, select, fieldset, table, progress, output).
-> Ship the native element. Zero ARIA.
A composition of native elements covers it (<dialog> + <button>
+ popovertarget; <fieldset><legend> + <input type=radio>;
<nav><ul><a>).
-> Ship the composition. Minimum ARIA only for the accessible name
(aria-labelledby or aria-label) when no <label>/<legend>/<summary>
can carry it.
No native HTML primitive matches (combobox with custom-rendered options,
treegrid, carousel, application menu, listbox with multi-select and
custom rows, treeview).
-> Layer ARIA on top of the closest neutral element (usually <div>
or <button>) following the relevant APG pattern.
APG pattern picker (one-line decisions)
| UI concept | Native HTML first | APG pattern if no native fit |
|---|---|---|
| Show/hide a section | <details><summary> |
Disclosure |
| Modal blocking dialog | <dialog> + showModal() |
Dialog (Modal) |
| Tabbed content panels | (no native) | Tabs |
| Dropdown select | <select> |
Combobox + Listbox |
| Autocomplete / type-ahead | <input list> + <datalist> |
Combobox with listbox popup (aria-activedescendant) |
| Single-choice in group | <input type=radio> + <fieldset> |
Radio Group |
| Multi-choice list | <select multiple> or checkboxes |
Listbox with aria-multiselectable="true" |
| Application command menu | (no native) | Menu / Menubar |
| Site navigation | <nav><ul><a> |
NONE (do NOT use Menu) |
| Notification banner | (no native) | Live region (role="status" or role="alert") |
| Image carousel | (no native) | Carousel |
| Hierarchical tree | (no native) | Tree |
| Hierarchical table | <table> for flat data |
Treegrid (only when hierarchy AND tabular both required) |
| Toggle (on/off) | <input type=checkbox> |
Switch (role="switch") |
| Progress feedback | <progress> or <output> |
role="progressbar" |
Labelling precedence (accessible name computation)
aria-labelledby > aria-label > native label (<label>, <legend>, <summary>, alt) > element content text > title.
Rules of thumb :
- Visible text label exists ->
aria-labelledbypointing to it. NEVERaria-label(it overrides and silently diverges from the visible text). - No visible text label, icon-only control ->
aria-label. - Supplementary description (announced after the name, often suppressible) ->
aria-describedby. - Form error ->
aria-errormessagepointing to the visible error message ANDaria-invalid="true"on the same field. Withoutaria-invalid="true",aria-errormessageis silently ignored.
Live regions cheat-sheet
| Attribute | Values | Use |
|---|---|---|
aria-live |
off (default), polite, assertive |
Politeness. polite for status; assertive ONLY for time-critical (session expiry, payment failure). |
aria-atomic |
false (default), true |
true re-reads the whole region on change. Use for short labelled regions ("Score : 5" -> "Score : 6"). |
aria-relevant |
additions, removals, text, all (default additions text) |
Which mutation types announce. Rarely overridden. |
aria-busy |
false (default), true |
Suppress announcements during multi-step DOM updates; clear when done. |
role="status" |
shortcut | Implicit aria-live="polite" + aria-atomic="true". |
role="alert" |
shortcut | Implicit aria-live="assertive" + aria-atomic="true". Per APG: Alert (verified 2026-05-19), alerts MUST NOT auto-dismiss and MUST be used sparingly. |
CRITICAL : the live region element MUST exist in the DOM BEFORE the content is inserted. Render an empty <div role="status" aria-live="polite" aria-atomic="true"></div> at page load; update its textContent to announce.
Decision Trees
Decision : native HTML or ARIA?
Is there a single HTML element that has the role and behavior you need?
yes -> ship it. NO role attribute. NO aria-* unless you need a label.
no -> next question.
Is there a composition of HTML elements that delivers the role and behavior?
yes -> ship the composition. Add aria-labelledby / aria-label only if no
native labelling element (<label>, <legend>, <summary>) can carry
the name.
no -> apply the relevant APG pattern. Use the MINIMUM set of roles and
states required by that pattern. Do not invent extras.
Did you find yourself adding role="button" / role="link" / role="heading"
to a <div> or <span>?
yes -> STOP. Use <button> / <a href> / <h1>-<h6>. ARIA does NOT add
keyboard, focus, type=submit, or default behaviors. Native does.
Decision : which labelling attribute?
Is there a visible text label for this control?
yes, label is a <label> for a form control -> connect via for= /
id=. No ARIA needed.
yes, label is some other visible text (heading, span)
-> aria-labelledby="<id of that text>"
no, the control is icon-only or text-free
-> aria-label="<short descriptive name>"
Need extra description beyond the name?
yes -> aria-describedby="<id of description>". Read after the name.
Users may suppress descriptions.
no -> nothing.
Need to surface a validation error message?
yes -> aria-errormessage="<id of error>" AND aria-invalid="true" on the
same control. Both are REQUIRED; aria-invalid alone announces
"invalid" but no message; aria-errormessage alone is silently
ignored.
no -> nothing.
Decision : which APG pattern for this UI?
See the table in Quick Reference. The full rationale per pattern (variants, keyboard model, focus model, common failures) is in methods.md §3 - §10.
Decision : Combobox popup type and focus mode?
What renders inside the popup?
Listbox / grid / tree of options. User types in the combobox input
while options highlight.
-> DOM focus stays on the combobox input.
aria-activedescendant references the highlighted option's id.
NEVER move DOM focus into the listbox; typing breaks otherwise.
A dialog (e.g. date picker calendar) where the user interacts with
multiple controls.
-> DOM focus moves into the dialog on open.
aria-haspopup="dialog" on the combobox.
Restore focus to the combobox on close.
Decision : Tabs activation mode (automatic or manual)?
Does activating a tab have a cost or side effect (network fetch, expensive
render, analytics event, route change)?
yes -> manual activation. Arrow keys move focus only;
Space / Enter activates. aria-selected updates on activation.
no -> automatic activation. Arrow keys move focus AND activate;
aria-selected updates on focus. Default per APG.
Patterns
Detailed per-pattern coverage is in methods.md. Brief index :
- Disclosure : button +
aria-expanded. Prefer<details><summary>when possible. - Dialog (Modal) :
role="dialog"+aria-modal="true"+aria-labelledby, focus trap, Escape, restore focus on close. Native<dialog>showModal()gives 3 of 4 for free (still need focus restore). - Tabs :
tablist/tab/tabpanel,aria-selected,aria-controls,aria-labelledbycross-reference, roving tabindex, automatic vs manual. - Combobox :
role="combobox"+aria-controls+aria-expanded+aria-autocomplete+aria-haspopup. Focus depends on popup type. - Listbox :
role="listbox"+role="option",aria-multiselectable,aria-selected, roving tabindex ORaria-activedescendant. - Menu / Menubar :
role="menu"/role="menubar"+menuitemvariants,aria-haspopup+aria-expanded, roving tabindex, NEVER for navigation links. - Radio Group :
role="radiogroup"+role="radio",aria-checked, single tabstop, arrows move focus AND select. - Tree :
role="tree"+role="treeitem",aria-expandedon parents only, roving tabindex, optionalaria-level/aria-posinset/aria-setsizewhen lazy. - Treegrid :
role="treegrid"+role="row"+role="gridcell", cell-focus OR row-focus mode, Left/Right collapse/expand, F2 edit mode. - Carousel :
role="region"+aria-roledescription="carousel", pause on focus AND hover, dynamic Stop/Start label (NOTaria-pressed), slide-picker uses Tabs pattern for the tabbed variant.
Full keyboard models, state matrices, and variant rules in methods.md.
Anti-Patterns Index
See anti-patterns.md for symptom + root cause + fix. Minimum eight cataloged : <div role="button"> instead of <button>, aria-label overriding visible text, redundant role (<nav role="navigation">), live region inserted at update time, aria-hidden on focusable background of a modal (use inert), tabs without roving tabindex, combobox moving DOM focus into listbox, role="menu" on navigation links, aria-errormessage without aria-invalid="true", role="alert" used for non-urgent polling, missing focus restore on dialog close.
Reference Links
- Methods and signatures : full ARIA 1.2 surface used in this skill, plus per-pattern role-state-keyboard tables (Carousel, Disclosure, Listbox, Menu, Radio, Tree, Treegrid, Dialog, Combobox, Tabs).
- Examples : working HTML snippets for accessible Tabs (roving tabindex + manual activation), Combobox with listbox popup (
aria-activedescendant), Disclosure with native<details>, live-region pattern, dialog focus restore. - Anti-patterns : eleven cataloged anti-patterns with WCAG references.
Cross-references
[[frontend-a11y-focus-keyboard-inert]]::focus-visible, roving tabindex mechanics, theinertattribute, programmatic focus and focus restore.[[frontend-a11y-motion-contrast-wcag22]]: WCAG 2.2 Success Criteria for contrast, motion, target size; pairs with ARIA labelling.[[frontend-impl-popover-dialog-anchor]]:<dialog>showModal(),popover, anchor positioning details.[[frontend-syntax-html5-semantic]]: native semantic elements (the "first" in first-rule-of-ARIA).[[frontend-syntax-html5-form]]:aria-invalid+aria-errormessageform-error binding,:user-invalidstyling.