ARIA Architecture
aria architecture aria architecture extends html semantics for complex widgets when native element aria architecture layers accessible names, roles, states,
Introduction
ARIA architecture layers accessible names, roles, states, and properties on HTML when native semantics are insufficient — custom tabs, comboboxes, live regions. Staff engineers treat ARIA as a state machine synced with visual UI — stale aria-expanded or wrong role breaks WCAG 4.1.2 catastrophically.
Business problem
ARIA misuse is worse than no ARIA: role=navigation on div wrapping non-nav, aria-label overriding visible text confusing SR users, redundant aria-role on native elements. Design systems need ARIA patterns spec'd per WAI-ARIA APG.
- APG: W3C Authoring Practices Guide — reference implementations for complex widgets.
- State sync: React state must mirror aria-expanded, aria-selected, aria-checked.
Why this feature exists
WAI-ARIA extends HTML when native elements don't exist (tabs, treegrid, combobox) or when visual design hides semantic structure temporarily (aria-hidden on modal backdrop).
- Five rules of ARIA: Don't use if native exists; don't change native semantics; all interactive must be keyboard accessible; no role on focusable without accessible name; hide decorative only.
Browser internals
Accessibility API mapping: aria-* attributes map to platform properties (IA2, AXAPI, AT-SPI). Browser fires events on aria state changes — SR announces when live region inserted.
- aria-hidden=true: Removes from tree — focusable descendants forbidden (focus must not enter hidden subtree).
- aria-live: polite vs assertive queue — misuse causes announcement spam.
Rendering workflow
Widget lifecycle: Mount → set roles/states → manage focus on open → update aria-selected on arrow keys → announce via live region on async completion → restore focus on close.
- Tab pattern: tablist > tab + tabpanel, roving tabindex, aria-controls linkage.
- Combobox: input + listbox popup — most complex APG pattern.
Feature deep dive
ARIA patterns for production widgets:
- Tabs: role=tablist, tab, tabpanel; aria-selected, aria-controls.
- Modal: dialog, aria-modal=true, focus trap, aria-labelledby title.
- Live region: role=status or aria-live=polite for toast notifications.
- Disclosure: button aria-expanded controls region id.
<div role="tablist" aria-label="Account sections"><button role="tab" id="tab-1" aria-selected="true" aria-controls="panel-1">Profile</button><button role="tab" id="tab-2" aria-selected="false" aria-controls="panel-2">Billing</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>
Accessibility analysis
ARIA review checklist: Every role from APG? States updated on every interaction? Focus management documented? aria-labelledby resolves to visible text? Tested with NVDA and VoiceOver?
- Anti-pattern: aria-label on button when visible text exists — overrides visible label incorrectly if mismatched.
SEO impact
aria-hidden content is excluded from accessibility tree — Google may still index visible hidden text; don't hide SEO keywords with aria-hidden (cloaking risk and a11y violation).
Security considerations
aria-live injection via XSS announces malicious content to SR users. Sanitize dynamic messages; CSP on inline handlers.
Performance impact
Live region spam on every poll update causes SR CPU churn and user abandonment — debounce announcements, use aria-busy during load.
Real production example
Composable modal ARIA contract in design system:
- Focus: showModal() moves focus inside; close() returns to trigger.
<dialog id="confirm" aria-labelledby="confirm-title" aria-describedby="confirm-desc"><h2 id="confirm-title">Delete item?</h2><p id="confirm-desc">This cannot be undone.</p><button type="button" value="cancel">Cancel</button><button type="submit" value="confirm">Delete</button></dialog>
Enterprise usage
Pattern library copies APG examples with team-specific styling — one tab implementation, not twelve variants. ARIA attribute allowlist in components.
Common production failures
aria-expanded stuck false while panel open — SR users don't know state. role=menu on site nav — wrong pattern (menu is application menu not site nav). Live region assertive on form validation every keystroke.
Architecture review questions
- Does widget match WAI-ARIA APG pattern exactly?
- Are ARIA states synced with React/Vue state on every transition?
- Focus trapped and restored in modals/menus?
- No aria-hidden on focused elements?
- Live regions debounced appropriately?
Hands-on project
Project: Implement accessible tabs per APG with keyboard arrows, Home/End, roving tabindex — test NVDA announcement of selected tab.
Interview questions
First rule of ARIA — explain with example.(Advanced)
If native HTML works, don't use ARIA. Use button not div role=button. Use input type=checkbox not div role=checkbox. ARIA adds maintenance burden and error surface — native elements get keyboard and state free.
Follow-up: When do you need role=tablist?
Difference between aria-label and aria-labelledby?(Advanced)
aria-label provides string name directly — use when no visible label (icon button with known action). aria-labelledby references element IDs — preferred when visible label exists; keeps visible and spoken name in sync.
Follow-up: Icon-only button pattern?
How test aria-live regions?(Advanced)
Manual SR required mostly — trigger async update, verify announcement order and politeness. Automated tools limited. Test assertive vs polite; ensure not firing on every keystroke; aria-atomic when whole message should read.
Follow-up: role=alert vs aria-live=assertive?
Try it yourself
Edit the HTML, CSS, or JS panels — the preview updates as you type.
Try it yourself
Summary
ARIA architecture extends HTML semantics for complex widgets when native elements fall short — staff engineers implement WAI-ARIA APG patterns with rigorous state sync, focus management, and screen reader validation.