可访问性
可访问性不是“加 aria 就行”,而是语义、键盘、焦点与反馈闭环共同决定的体验质量。
从语义开始
可访问性最省心的方式是把“基础但无聊”的事做对:正确的 HTML 元素、label 与关联关系。ARIA 是补充,不是地基。
- 动作用 <button>,导航用 <a href>。不要用 div + onClick 冒充按钮。
- 每个输入都需要 label(可见或 aria-label),并且 name/id 稳定。
- 如果你加了 ARIA,确保 role/state 正确,并且与 UI 同步更新。
键盘交互模式
把常见组件的键盘行为标准化,是最快“把可访问性做对”的方式。
- 菜单/列表:方向键移动焦点;Enter 选择;Esc 关闭。
- 对话框:打开后焦点进入对话框;关闭后回到触发按钮。
- Tabs:方向键切换 tab;激活的 tab 控制面板显示。
焦点管理
- 焦点必须可见:不要移除 outline;如果替换,也要可见且对比足够。
- 路由切换后,在合适场景把焦点移动到新页面标题。
- 表单提交失败时,聚焦第一个错误字段,并宣读错误汇总。
让状态可感知(aria-live、role)
辅助技术用户需要“变化反馈”:加载、错误、保存成功等都要能被感知。
TypeScript
export function SaveStatus({ status }: { status: 'idle' | 'saving' | 'saved' | 'error' }) {
return (
<div aria-live="polite">
{status === 'saving' ? 'Saving…' : null}
{status === 'saved' ? 'Saved.' : null}
{status === 'error' ? <span role="alert">Save failed.</span> : null}
</div>
);
}示例:最小可访问对话框
对话框至少要有:正确的 role、aria-modal、可标注的标题、打开时聚焦、关闭时焦点回收。
TypeScript
import { useEffect, useId, useRef } from 'react';
export function Dialog({
open,
title,
onClose,
children,
}: {
open: boolean;
title: string;
onClose: () => void;
children: React.ReactNode;
}) {
const titleId = useId();
const panelRef = useRef<HTMLDivElement | null>(null);
const triggerRef = useRef<HTMLElement | null>(null);
useEffect(() => {
if (!open) return;
triggerRef.current = document.activeElement as HTMLElement | null;
panelRef.current?.focus();
}, [open]);
useEffect(() => {
if (open) return;
triggerRef.current?.focus();
}, [open]);
if (!open) return null;
return (
<div
role="dialog"
aria-modal="true"
aria-labelledby={titleId}
className="fixed inset-0 bg-black/50 flex items-center justify-center p-4"
onKeyDown={(e) => {
if (e.key === 'Escape') onClose();
}}
>
<div
ref={panelRef}
tabIndex={-1}
className="bg-white rounded p-4 w-full max-w-md dark:bg-gray-800"
>
<div className="flex items-center justify-between gap-3">
<h2 id={titleId} className="font-bold">
{title}
</h2>
<button type="button" onClick={onClose} aria-label="Close">
×
</button>
</div>
<div className="mt-3">{children}</div>
</div>
</div>
);
}检查清单
- 所有可交互元素都能通过 Tab 到达。
- 焦点可见,关闭弹窗后能回到合理位置。
- 表单错误可被宣读(role=alert)并与输入关联(aria-describedby)。
- 没有键盘陷阱:Esc 可关闭弹窗,焦点能回到合理位置。
- 交互控件都有可访问名称(label/aria-label)。