More Than Just Buttons: A Frontend Engineer's Exploration of Accessibility Frameworks
Architect Accessibility from Day 0, focus/live-region utilities, keyboard-friendly components, axe-core CI tests, and a "mouse-free" review culture.
Join the DZone community and get the full member experience.
Join For FreeAccessibility is often treated as actions that have to be implemented like checkboxes. In this scenario, contrast, alt images, and tagging will create structure. However, in real-world scenarios that involve advanced frontend systems, accessible interfaces are products of architectural decisions.
In a practical scenario, as a senior engineer, there are multiple factors of influence apart from the coding aspects. The impact radiates to the workflows of development, component systems, and patterns, which are used or created by others. If there is no strategy incorporated during construction for accessibility, there will be difficulties or bridge gaps that need to be crossed when trying to patch this problem later on, causing multiple regressions.
In this article, I present a solution that deals with practical coding and focuses on efficiency of use with accessibility on the frontend architecture to go beyond minimal requirements, to create and enhance interaction with inclusivity.
1. Ease of Access Focus On Custom Components to Ensure All Users Have Equal Access
Final designs of components play a crucial role and may be the first step to ensuring access for all users. Custom dropdowns like the ones described for modals, tooltips and other like widgets require to be sculpted in a manner where behavior which is considered accessible is put into the design structure, which requires far more than just adding attributes of ARIA tags.
Example: Dropdown With Keyboard Usability and ARIA Role
import { useState, useRef } from "react";
export function AccessibleDropdown({ label, options }: { label: string; options: string[] }) {
const [isOpen, setIsOpen] = useState(false);
const [highlighted, setHighlighted] = useState<number | null>(null);
const buttonRef = useRef<HTMLButtonElement>(null);
return (
<div>
<button
aria-haspopup="listbox"
aria-expanded={isOpen}
ref={buttonRef}
onClick={() => setIsOpen(!isOpen)}
onKeyDown={(e) => {
if (e.key === "ArrowDown") {
setIsOpen(true);
setHighlighted(0);
}
}}
>
{label}
</button>
{isOpen && (
<ul role="listbox" aria-activedescendant={`option-${highlighted}`}>
{options.map((option, idx) => (
<li
key={option}
id={`option-${idx}`}
role="option"
aria-selected={highlighted === idx}
tabIndex={-1}
onMouseEnter={() => setHighlighted(idx)}
onClick={() => {
setIsOpen(false);
buttonRef.current?.focus();
}}
>
{option}
</li>
))}
</ul>
)}
</div>
);
}
Benefits:
- Keyboard navigation: It supports
ArrowDownand restores focus. - Screen reader compatible: It makes uses of roles,
aria-haspopupas well asaria-selected. - Reusable: Encapsulates logic so product engineers don’t need to reimplement accessibility behavior each time.
2. Management of Focus, Announcements and Dynamic Content
Accessibility is often lost with dynamic content such as modals, toasts, or tabbed interfaces whenever focus is not handled appropriately. Both screen readers and users who rely on keyboards have to navigate through a set context and order.
Example: Introducing Status Announcements With Live Region Hooks
import { useEffect, useState } from "react";
export function useLiveRegion() {
const [message, setMessage] = useState("");
useEffect(() => {
if (message) {
const timeout = setTimeout(() => setMessage(""), 1000);
return () => clearTimeout(timeout);
}
}, [message]);
return {
announce: (text: string) => setMessage(text),
region: (
<div
role="status"
aria-live="polite"
style={{ position: "absolute", left: "-9999px", height: 0, overflow: "hidden" }}
>
{message}
</div>
),
};
}
Usage:
const { announce, region } = useLiveRegion();
useEffect(() => {
if (formSubmitted) announce("Form submitted successfully.");
}, [formSubmitted]);
return (
<>
{region}
{/* rest of your UI */}
</>
);
Benefits:
- Non-intrusive: Does not detract attention from users who can see.
- Effectively: Delivers important notifications to the users of reading aids.
- Reusable: Wraps best practices into a clean hook thus no need to redeployed all over again.
3. Testing And Capturing Accessibility Regressions At Scale
Accessibility regressions creep in without manual auditing.
Example: CI friendly A11y Testing Using Playwright and Axe-Core
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test('Homepage should have no accessibility violations', async ({ page }) => {
await page.goto('http://localhost:3000');
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
Benefits:
- Integration with CI: solves the problem of merging new issues.
- Scalable: works across pages and flows.
- Immediate feedback: provides developers with the learning process as they code.
4. Real-World Example: Keyboard Navigation Regression
We had a handover and implemented a form with several panels in one of the production systems. The testing passed it, but screen reader users were completely stuck not being able to reach the “Next” button as focus was stuck in a closed accordion.
Fix: Focus Trap Utility
export function trapFocus(container: HTMLElement) {
const focusable = container.querySelectorAll<HTMLElement>(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const first = focusable[0];
const last = focusable[focusable.length - 1];
container.addEventListener("keydown", (e) => {
if (e.key === "Tab") {
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
});
}
Benefits:
- Avoids broken tab order.
- Enhances modal and wizard UX.
- Encapsulated logic: Easier to test and refactor.
5. Cultivating Inclusive Engineering Culture
Architecture generates leverage but culture maintains it. As emblematic engineers, we have the ability to make accessible, employable universally by:
- Conducting accessibility focused design reviews
- Encouraging “Can this be operated without a mouse?” to be used
- Designing reusable building blocks that assume non-strict conditions and edge cases.
- Taping screener presentations for PMs and designers
- Trained in tracking the accessibility of disabling features as regressions
Embedding the conversation around code reframes accessibility to “not extra work, part of the process.”
Conclusion
Accessibility isn’t about checking a box and closing off a list. It's a way of thinking that stretches across elements, designs and teams. As engineers who care about quality and inclusion, we are equipped — and accountable for building ways for systems to inclusively work for everyone.
Consider this the next time you are designing a component or checking a pull request: instead of simply asking, “Does it perform its function?” try asking, “Can all of my users interact with this, even the ones that I will never meet?”
Opinions expressed by DZone contributors are their own.
Comments