Why Accessibility is Not About "Checkboxes," But About People
Let's be honest: when we hear "accessibility" (or a11y), many of us imagine endless lists of requirements, boring checks, and the phrase "well, it's for people with disabilities." But the truth is that accessibility is about everyone. Have you ever tried to order pizza on the go, holding your phone with one hand? Or sat in the subway with poor internet, where the site loads for an eternity? Those are accessibility issues.
In this article, we'll talk about how TypeScript can become your best friend in creating accessible web applications. Not just "adding tags," but building an architecture where inclusivity is not a feature, but a foundation.
TypeScript as a Shield Against a11y Errors
Many think TypeScript is only about data types and protection against bugs. But in reality, strong typing can also save the day in accessibility matters. Let's break it down with examples.
Typing ARIA Attributes
One of the most common problems is incorrect values for ARIA attributes. Have you ever written role="button" on a div? And then forgot to add tabindex? TypeScript can catch this at compile time.
// Bad: no protectionfunction createButton(label: string) { return `<div role="button">${label}</div>`;}
// Good: typing ARIAtype AriaRole = 'button' | 'navigation' | 'alert' | 'dialog';
interface AriaAttributes { role: AriaRole; 'aria-label'?: string; 'aria-expanded'?: boolean; tabIndex?: number;}
function createAccessibleButton(label: string, aria: AriaAttributes) { // Now TypeScript will check that the role is valid return { tag: 'div', attrs: aria, content: label };}
// Error! 'btn' is not part of the AriaRole type// createAccessibleButton('Click me', { role: 'btn' });Can you feel the difference? TypeScript won't let you accidentally write role="btutton" or forget a required attribute. It's like a personal assistant whispering, "Hey, are you sure this is accessible?"
Strict Checking of Focus States
Another pain point is focus management. Especially in SPAs, where the page doesn't reload. TypeScript helps create safe wrappers.
// Define which elements can receive focustype FocusableElement = HTMLInputElement | HTMLButtonElement | HTMLAnchorElement | HTMLSelectElement | HTMLTextAreaElement;
function safelySetFocus(element: FocusableElement | null): void { if (element && document.contains(element)) { element.focus(); } else { console.warn('Element not found in the DOM or cannot receive focus'); }}
// Usageconst modal = document.querySelector('.modal__close-btn');safelySetFocus(modal as FocusableElement | null);Notice: we don't just call .focus(), but check whether the element exists and can be focused. This prevents errors that could leave a screen reader user in a void.
Building Architecture with a11y at Its Core
Now let's move on to more advanced things. How to ensure that accessibility is baked into your project's architecture, rather than added "on the fly" right before release?
Typed Props for Components
Create a base interface for all your components. It's like a constitution for accessibility.
// Base interface for all UI componentsinterface AccessibleComponentProps { id?: string; className?: string; ariaLabel?: string; ariaDescribedby?: string; role?: string; tabIndex?: number; isDisabled?: boolean; onClick?: () => void; onKeyDown?: (e: React.KeyboardEvent) => void;}
// Specific button componentinterface ButtonProps extends AccessibleComponentProps { variant