Practical Patterns for Real-World Components
In Part 1, we discussed why ARIA exists and why accessibility is not optional in modern React applications.
In this part, we move from principles to practice.
ARIA becomes meaningful only when applied to real components, real state, and real user interactions.
This article focuses on common UI patterns that React developers build every day — and how ARIA makes them usable for everyone.
1. Buttons: The Simplest Thing We Still Get Wrong
The problem
<div onClick={onSubmit}>Submit</div>
This works visually, but it breaks:
- Keyboard navigation
- Screen readers
- Expected behavior (Enter / Space)
The correct approach
<button onClick={onSubmit}>
Submit
</button>
✅ Keyboard support
✅ Screen reader support
✅ No ARIA required
If a native element exists, use it. Always.
2. Custom Buttons: When ARIA Becomes Necessary
Sometimes design systems force us to build custom components.
Minimum acceptable pattern
<div
role="button"
tabIndex={0}
aria-disabled={disabled}
onClick={handleClick}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
handleClick();
}
}}
>
Submit
</div>
ARIA here is not decoration — it is damage control.
If you forget keyboard handling, ARIA becomes a lie.
3. Dropdowns & Menus: Communicating State
The problem
A dropdown visually opens, but assistive technology has no idea what happened.
The pattern
<button
aria-haspopup="menu"
aria-expanded={isOpen}
aria-controls="menu"
onClick={toggle}
>
Options
</button>
<ul id="menu" role="menu" hidden={!isOpen}>
<li role="menuitem">Edit</li>
<li role="menuitem">Delete</li>
</ul>
What ARIA communicates:
- This button controls a menu
- The menu is open or closed
- Items are selectable
ARIA describes relationships and state — not visuals.
4. Tabs: One of the Most Misimplemented Patterns
Tabs look simple. They are not.
Correct structure
<div role="tablist">
<button
role="tab"
aria-selected={activeTab === 0}
onClick={() => setActiveTab(0)}
>
Profile
</button>
<button
role="tab"
aria-selected={activeTab === 1}
onClick={() => setActiveTab(1)}
>
Settings
</button>
</div>
<div role="tabpanel" hidden={activeTab !== 0}>
Profile content
</div>
<div role="tabpanel" hidden={activeTab !== 1}>
Settings content
</div>
Key rules:
- Only one tab is selected
- Tab panels must match tabs
- Keyboard navigation must be supported (Left / Right arrows)
ARIA defines meaning, but you must define behavior.
5. Forms: Error Handling That Respects Users
The problem
Visual error messages that screen readers never announce.
The solution
<input
aria-invalid={!!error}
aria-describedby={error ? 'email-error' : undefined}
/>
{error && (
<span id="email-error">
Invalid email address
</span>
)}
What this achieves:
- Errors are announced when focus enters the input
- Context is preserved
- Users are not forced to guess
Accessibility here is not about compliance — it is about respect.
6. Modals: Focus, Isolation, and Control
A modal without ARIA is a trap.
Minimum requirements
role="dialog"aria-modal="true"- Focus moves into the modal
- Focus is trapped
- Focus returns on close
<div role="dialog" aria-modal="true">
<h2 id="title">Confirm Action</h2>
<p id="desc">Are you sure?</p>
</div>
Without this:
- Screen readers read the background
- Keyboard users get lost
- Users cannot escape
If users cannot exit your modal, your UI has failed.
7. React State Is the Source of Truth
ARIA values must always reflect React state.
aria-expanded={isOpen}
aria-selected={isActive}
aria-busy={isLoading}
Never:
- Hard-code ARIA values
- Leave outdated states
- Let UI and ARIA drift apart
ARIA must tell the truth — always.
8. Accessibility as a Code Review Standard
A mature team does not ask:
“Did we add ARIA?”
They ask:
- Can this be used with keyboard only?
- Does state change get announced?
- Are native elements used correctly?
- Is ARIA describing reality?
Accessibility is not a task.
It is a definition of done.
9. Conclusion: ARIA as a Craft, Not a Checklist
ARIA is not about attributes.
It is about empathy translated into code.
When you use ARIA correctly:
- Your components become clearer
- Your state management becomes more honest
- Your UI becomes more resilient
A professional React developer does not ask how the UI looks.
They ask who it leaves behind.