ARIA Roles
ARIA Roles
Define the purpose of elements for assistive technologies.
Landmark Roles
<div role="banner">{/* header */}</div>
<nav role="navigation">{/* nav */}</nav>
<main role="main">{/* main */}</main>
<aside role="complementary">{/* aside */}</aside>
<footer role="contentinfo">{/* footer */}</footer>
Widget Roles
// Button
<button role="button">Click me</button>
<div role="button" tabIndex={0}>Click me</div>
// Tab
<div role="tablist">
<button role="tab" aria-selected="true">Tab 1</button>
<button role="tab" aria-selected="false">Tab 2</button>
</div>
<div role="tabpanel">Panel 1</div>
// Menu
<ul role="menu">
<li role="menuitem">Option 1</li>
<li role="menuitem">Option 2</li>
</ul>
// Dialog
<div role="dialog" aria-modal="true">
<h2>Dialog Title</h2>
<p>Dialog content</p>
</div>
Live Region Roles
// Status updates
<div role="status" aria-live="polite">
{loading ? 'Loading...' : `${count} results`}
</div>
// Alert messages
<div role="alert">
{error && 'Error: Please fix the issues below'}
</div>
// Timer
<div role="timer" aria-live="off">
{formatTime(remaining)}
</div>
Role Usage Guide
| Role | Use When |
|---|---|
| button | Clickable element |
| link | Navigates to URL |
| tab | Tab in tabbed interface |
| menuitem | Item in menu |
| dialog | Modal or popup |
| alert | Urgent message |
| status | Status update |
| progressbar | Loading indicator |
ARIA States
ARIA States
Indicate current state of elements.
Common States
// Expanded (accordion)
<button aria-expanded={isOpen}>
{isOpen ? 'Collapse' : 'Expand'}
</button>
// Selected (tabs)
<button role="tab" aria-selected={isActive}>
Tab {index}
</button>
// Checked (checkbox)
<input type="checkbox" aria-checked={isChecked} />
<div role="checkbox" aria-checked="mixed">Partial</div>
// Disabled
<button disabled aria-disabled="true">Cannot click</button>
// Hidden
<div hidden aria-hidden="true">Not visible</div>
// Current (navigation)
<a href="/current" aria-current="page">Current Page</a>
Form States
// Required
<input required aria-required="true" />
// Invalid
<input
aria-invalid={hasError}
aria-describedby="error-message"
/>
{hasError && <span id="error-message">Error text</span>}
// Error
<div role="alert" aria-live="assertive">
{errorMessage}
</div>
// Loading
<button aria-busy={isLoading} disabled={isLoading}>
{isLoading ? 'Loading...' : 'Submit'}
</button>
Composite States
// Tree view
<div role="treeitem" aria-expanded={isExpanded} aria-selected={isSelected}>
{label}
</div>
// Grid cell
<div role="gridcell" aria-selected={isActive}>
{value}
</div>
// Listbox option
<div role="option" aria-selected={isSelected} aria-checked={isChecked}>
{option.label}
</div>
ARIA Properties
ARIA Properties
Provide additional information about elements.
Labeling
// aria-label (visible text not suitable)
<button aria-label="Close dialog">×</button>
<input aria-label="Search products" />
// aria-labelledby (reference to visible text)
<h2 id="section-title">Section Title</h2>
<div aria-labelledby="section-title">Content</div>
// aria-describedby (additional description)
<input
aria-describedby="password-hint"
/>
<p id="password-hint">Must be 8+ characters</p>
// aria-errormessage (error text)
<input
aria-invalid="true"
aria-errormessage="email-error"
/>
<span id="email-error">Invalid email</span>
Relationships
// Controls (element this controls)
<button aria-controls="menu-1">Menu</button>
<ul id="menu-1" role="menu">...</ul>
// Labelledby (label reference)
<div role="dialog" aria-labelledby="dialog-title">
<h2 id="dialog-title">Title</h2>
</div>
// Describedby (description reference)
<input aria-describedby="input-desc" />
<span id="input-desc">Help text</span>
// Owns (owns another element)
<div aria-owns="popup-1">Trigger</div>
<div id="popup-1" role="tooltip">Popup content</div>
Live Regions
// Polite (waits for user to finish)
<div aria-live="polite">
{statusMessage}
</div>
// Assertive (interrupts immediately)
<div aria-live="assertive">
{errorMessage}
</div>
// Off (no announcement)
<div aria-live="off">
{debugInfo}
</div>
// Atomic (announce entire region)
<div aria-live="polite" aria-atomic="true">
{announcement}
</div>
// Relevant (what changes to announce)
<div aria-live="polite" aria-relevant="additions removals">
{list.map(item => <Item key={item.id} item={item} />)}
</div>
Drag and Drop
<div
draggable
aria-grabbed={isGrabbed}
aria-dropeffect="copy"
>
Draggable item
</div>
<div aria-dropeffect="copy" aria-dropactive={isOver}>
Drop target
</div>
},
{
"id": "ch4",
"title": "When NOT to Use ARIA",
"content": "## When NOT to Use ARIA
First rule of ARIA: Don't use ARIA if you can use native HTML.
Use Native HTML Instead
// ❌ Bad: ARIA button
<div role="button" tabIndex={0} onClick={handleClick}>
Click me
</div>
// ✅ Good: Native button
<button onClick={handleClick}>Click me</button>
// ❌ Bad: ARIA navigation
<div role="navigation" aria-label="Main">
<a href="/">Home</a>
</div>
// ✅ Good: Native nav
<nav aria-label="Main">
<a href="/">Home</a>
</nav>
// ❌ Bad: ARIA checkbox
<div
role="checkbox"
aria-checked={checked}
onClick={() => setChecked(!checked)}
onKeyDown={(e) => {
if (e.key === ' ') setChecked(!checked);
}}
tabIndex={0}
/>
// ✅ Good: Native checkbox
<input
type="checkbox"
checked={checked}
onChange={(e) => setChecked(e.target.checked)}
/>
ARIA Myths
Myth: More ARIA = more accessible
Truth: Wrong ARIA is worse than no ARIAMyth: ARIA fixes everything
Truth: Semantic HTML is betterMyth: ARIA is required
Truth: Often not needed with proper HTML
When ARIA IS Needed
- Custom widgets with no HTML equivalent
- Complex interactive patterns
- Dynamic content updates
- Enhancing semantic HTML
Testing ARIA
# Run accessibility tests
npm run test:a11y
# Use axe-core
npx axe http://localhost:3000
# Use lighthouse
npx lighthouse http://localhost:3000 --only-categories=accessibility
Practice Problems
Create a reusable React component implementing ARIA. Include proper state management and accessibility.
Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where neededWrite unit and integration tests for ARIA using React Testing Library.
Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility testsOptimize ARIA for performance. Consider memoization, code splitting, and bundle size.
Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysisQuiz
1. What is the first rule of ARIA?
2. When should you use aria-live="assertive"?
3. What is the primary purpose of ARIA?
4. What is a common mistake when implementing ARIA?
Flashcards
Question
What is ARIA?
Click to reveal answer
Answer
Accessible Rich Internet Accessibility - attributes that make web content more accessible.
Question
What are ARIA roles?
Click to reveal answer
Answer
Attributes that define the purpose of an element for assistive technologies.
Question
What are ARIA states?
Click to reveal answer
Answer
Attributes that indicate the current state of an element (expanded, selected, checked).
Question
When should you NOT use ARIA?
Click to reveal answer
Answer
When native HTML elements provide the same functionality and semantics.
Question
What is ARIA?
Click to reveal answer
Answer
ARIA is a key concept in frontend development.
Revision Notes
Key Takeaways
- 1.Don't use ARIA if native HTML works
- 2.ARIA roles define element purpose
- 3.ARIA states indicate current condition
- 4.ARIA properties provide additional info
- 5.Test with screen readers and axe-core
Interview Tips
- •Explain the first rule of ARIA
- •Discuss when ARIA is needed vs not needed
- •Know common ARIA attributes
Cheat Sheet
ARIA Cheat Sheet
First Rule
Don't use ARIA if native HTML works.
Common Roles
- button, link, tab, menuitem
- dialog, alert, status
- tablist, tabpanel, treeitem
Common States
- aria-expanded
- aria-selected
- aria-checked
- aria-disabled
- aria-invalid
Common Properties
- aria-label
- aria-labelledby
- aria-describedby
- aria-live
- aria-hidden