为什么无障碍不是“打勾”,而是关乎人
老实说:当我们听到“无障碍”(或 a11y)时,很多人会想到无尽的需求清单、枯燥的检查,以及“嗯,这是给有障碍的人用的”这种说法。但事实是,可访问性关乎每一个人。你是否曾尝试单手拿着手机在路上订披萨?或者在地铁里网络不好,网站加载半天?这些就是可访问性问题。
在本文中,我们将讨论TypeScript 如何成为您创建可访问 Web 应用程序的最佳伙伴。不仅仅是“添加标签”,而是构建一种架构,让包容性不是一项功能,而是基础。
TypeScript 作为 a11y 错误的盾牌
许多人认为 TypeScript 只关乎数据类型和防止错误。但实际上,强类型在可访问性方面也能提供保护。让我们通过示例来了解。
ARIA 属性的类型化
最常见的问题之一是 ARIA 属性值错误。您是否曾在 div 上写过 role="button"?然后忘了添加 tabindex?TypeScript 可以在编译阶段捕获这些问题。
// 不好:没有保护
function createButton(label: string) {
return `<div role="button">${label}</div>`;
}
// 好:对 ARIA 进行类型化
type AriaRole = 'button' | 'navigation' | 'alert' | 'dialog';
interface AriaAttributes {
role: AriaRole;
'aria-label'?: string;
'aria-expanded'?: boolean;
tabIndex?: number;
}
function createAccessibleButton(label: string, aria: AriaAttributes) {
// 现在 TypeScript 会检查角色是否有效
return {
tag: 'div',
attrs: aria,
content: label
};
}
// 错误!'btn' 不属于 AriaRole 类型
// createAccessibleButton('点击我', { role: 'btn' });
感受到区别了吗?TypeScript 不会让您意外写出 role="btutton" 或忘记必需属性。这就像一位私人助理在低语:“嘿,你确定这是可访问的吗?”
焦点状态的严格检查
另一个痛点——焦点管理。尤其是在 SPA 中,页面不会重新加载。TypeScript 有助于创建安全包装器。
// 定义哪些元素可以获取焦点
type FocusableElement = HTMLInputElement | HTMLButtonElement | HTMLAnchorElement | HTMLSelectElement | HTMLTextAreaElement;
function safelySetFocus(element: FocusableElement | null): void {
if (element && document.contains(element)) {
element.focus();
} else {
console.warn('元素不在 DOM 中或无法获取焦点');
}
}
// 使用
const modal = document.querySelector('.modal__close-btn');
safelySetFocus(modal as FocusableElement | null);
请注意:我们不只是调用 .focus(),而是检查元素是否存在以及是否可以被聚焦。这可以防止那些可能让屏幕阅读器用户陷入空白的错误。
构建以 a11y 为核心的架构
现在让我们进入更高级的内容。如何让可访问性嵌入到项目的架构中,而不是在发布前临时拼凑?
组件的类型化属性
为所有组件创建一个基础接口。这就像可访问性的宪法。
// 所有 UI 组件的基础接口
interface AccessibleComponentProps {
id?: string;
className?: string;
ariaLabel?: string;
ariaDescribedby?: string;
role?: string;
tabIndex?: number;
isDisabled?: boolean;
onClick?: () => void;
onKeyDown?: (e: React.KeyboardEvent) => void;
}
// 具体的按钮组件
interface ButtonProps extends AccessibleComponentProps {
variant