When I started building interfaces, accessibility felt like an afterthought. You ship fast, add a few aria-*
attributes, and move on. But the deeper I got into building design systems, the more I realized how foundational accessibility is.
Building accessible UIs isn't just about aria-labels
, it's about design systems, keyboard-first thinking, semantic HTML, correct API patterns, and thinking about the users who rely on assistive technologies.
Today, I want to share what building accessibility-first frontends really looks like, a deep dive into building accessible frontend apps beyond the basics, rooted in WCAG, ARIA guidelines, semantic HTML, and thoughtful component APIs. We'll lean on official specs from W3C and real-world insights from building production-grade UIs.
Accessibility is not an add-on. It's a design constraint
Accessibility begins before the code. It starts with how we design interactions, structure our component APIs, and handle focus, motion, and visual contrast.
The key principles of accessible design that you should consider based on the Web Content Accessibility Guidelines (WCAG):
- Perceivable: UI must be presentable to users in ways they can perceive (e.g., screen readers, color contrast, text alternatives).
- Operable: UI components must be usable with keyboards and other input devices.
- Understandable: UI should be predictable and consistent. This includes using simple language, consistent navigation, and predictable behavior.
- Robust: UI should be compatible with a wide range of user agents, including screen readers and old browsers.
These are the four WCAG success principles, and you should keep them in mind throughout your design and development process.
Visual Design Tips from WCAG
To create accessible visual designs, consider the following guidelines based on WCAG:
- Maintain a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text (see WCAG contrast)
- Ensure text can be resized without loss of content or functionality.
- Provide visible focus styles for interactive elements.
- Maintain a consistent layout across your application.
- Avoid fast, flashing animations or honor user motion preferences.
- Use semantic HTML elements (like
<header>
,<nav>
,<main>
, and<footer>
) to convey meaning and structure.
Semantics and Roles: The First Line of Accessibility
The fastest path to accessibility is using native HTML elements correctly, they come with built-in accessibility features that work out of the box.
For example, using <button>
instead of a <div>
with a click handler provides built-in keyboard support and focus management. Similarly, using <form>
, <input>
, and <label>
elements ensures screen readers interpret your forms correctly.
Avoid repurposing <div>
or <span>
unless you absolutely must, and if you do, add ARIA roles and keyboard interactivity (more on this below).
What the heck is ARIA?
ARIA (Accessible Rich Internet Applications) is a set of attributes that allows you to describe UIs for assistive technologies when native HTML falls short.
Use ARIA when:
- You build custom UI components (e.g., combobox, modal, tooltip).
- You need to describe relationships (e.g.,
aria-labelledby
,aria-controls
). - You want to mark dynamic content (e.g.,
aria-live
,aria-busy
).
Be cautious, improper use of ARIA can harm accessibility, so always prefer native HTML semantics first and use ARIA to enhance when necessary.
Here's a small example of a custom Dialog:
This example uses role="dialog"
to indicate a dialog, aria-modal="true"
to specify it is modal, and aria-labelledby
to associate the dialog with its title.
Keyboard Navigation: Core of Operability
Keyboard access is non-negotiable, not just for screen reader users, but also for power users, motor-impaired users, and accessibility testers. Here's a quick overview of required keyboard support for common UI components:
UI Component | Required Keyboard Support |
---|---|
Button | Enter, Space |
Dialog | Escape to close, Tab/Shift+Tab to navigate focus |
Tabs | ArrowRight / ArrowLeft |
Combobox | ArrowDown, ArrowUp, Enter, Escape |
Listbox | Arrow keys, type-ahead search |
Tooltip | Appear on focus/hover, close on Escape |
Here's a simple example of trapping focus inside a dialog to keep keyboard navigation contained:
For more in-depth guidance, check out the W3C Keyboard Interaction Guidelines.
Headless UI Libraries and API Design Patterns
Libraries like React Aria, Radix UI, and Base UI provide unopinionated, accessibility-first building blocks. They separate behavior from style, allowing you to:
- Implement your design system.
- Stay fully accessible.
- Maintain semantic control.
What Makes a Good Headless Component API?
- Accepts semantic HTML structure.
- Encourages correct ARIA roles out of the box.
- Manages keyboard and focus logic.
- Is composable (e.g., split buttons, nested popovers).
Screen Reader Considerations
Screen readers are a primary way many users interact with the web, and they are using the accessibility tree, which is built from:
- DOM + ARIA attributes.
- HTML element roles.
- Labeling relationships (like
aria-labelledby
,<label for=...>
).
Some tips for building screen reader-friendly components:
- Use
aria-live="polite"
for dynamic updates (like toasts, cart changes). - Always ensure visible labels are connected with
for
oraria-labelledby
. - Announce loading states with
aria-busy="true"
.
For more, see the WAI Live Regions Guide.
Testing Accessibility
Testing accessibility is crucial. Use both automated and manual testing methods to ensure your application meets accessibility standards.
Manual Testing:
- Keyboard navigation only.
- Use screen readers: NVDA (Windows), VoiceOver (Mac), or TalkBack (Android).
- Enable high contrast mode in your OS.
Automated Testing:
Keep in mind automation catches only about ~30-40% of issues. Manual checks and user testing with people with disabilities remain essential.
Modern HTML, CSS, and JS Features for A11y
Modern web standards have introduced many features that enhance accessibility out of the box:
HTML Features
<dialog>
: Native accessible modals with focus management. Building a Native HTML Dialog<details>
&<summary>
: Expandable sections with built-in toggle a11y.inert
attribute: Disables interaction and focus in background contentpopover
attribute & Popover API: Declarative popovers with focus handling.<output>
: Semantic output for dynamic content results.
CSS Features
:focus-visible
: shows outlines only during keyboard nav.@media (prefers-reduced-motion: reduce)
: disables transitions/animations.@media (forced-colors: active)
: detect Windows high contrast mode.@media (prefers-color-scheme)
: auto light/dark theme support.
JS Features
- Use
requestIdleCallback()
for low-priority DOM updates. - Use
IntersectionObserver
instead of scroll listeners. - Use focus management libraries like focus-trap, or implement manually.
Final Thoughts
Accessibility is not extra work; it's core to good design. Every dialog, listbox, and dropdown you build is an opportunity to design with empathy. If you can use semantic HTML, do it. If you must go custom, follow ARIA patterns, and always test with real users and tools.