关于 web-accessibility-web-accessibility
这是为构建无障碍 Web 界面提供的实践指南,面向设计和前端开发人员。内容围绕 WCAG 2.2 AA,涵盖语义 HTML、键盘操作、焦点、对比度和辅助技术测试。
主要功能
- 01优先使用语义 HTML
- 02检查键盘可达性与焦点
- 03为控件提供可访问名称
- 04满足文本与界面对比度要求
- 05处理动态内容通知和颜色信息
适用场景
- 01设计可键盘操作的页面
- 02为表单字段与错误提供辅助信息
- 03实现对话框的焦点管理
- 04审查 WCAG 2.2 AA 界面
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ agents-inc/ skills --skill web-accessibility-web-accessibility安装后在 Claude Code 中输入/web-accessibility-web-accessibility确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
metadata.yamlreference.mdexamples/color.mdcore.mdfocus.mdforms.mdscreen-reader.mdtables.mdtesting.mdtouch-targets.md详细介绍
无障碍
快速指南: 无障碍的大部分问题,都可以通过选择合适的元素来解决:
<button>自带焦点支持、键盘激活方式和角色,而<div>需要十几行代码才能模拟出来。元素无法提供的,是其余这些方面——为每个控件提供名称、显示可见的焦点指示器、确保文本对比度达到 4.5:1、在内容变化时进行播报,以及绝不单靠颜色传达信息。以 WCAG 2.2 AA 级为目标。自动化检查只能发现大约一半的问题,因此键盘检查不可或缺。
详细资源:
- examples/core.md — 跳过链接、地标、语义结构、按钮与链接的区别
- examples/forms.md — 标签、错误处理、必填字段、列表框规范
- examples/focus.md — 对话框、焦点捕获与恢复、焦点指示器
- examples/color.md — 对比度、不依赖颜色的状态提示、链接样式
- examples/tables.md — 表头、标题、可排序列
- examples/touch-targets.md — 目标尺寸与间距
- examples/screen-reader.md — 视觉隐藏文本、装饰性内容
- examples/testing.md — 自动化审查、基于角色的查询、手动检查
- reference.md — WCAG 2.2 成功准则、无障碍名称规则、审查规则 ID、屏幕阅读器
<critical_requirements>
编写 UI 代码前
优先使用语义元素,而不是 ARIA 属性。 <button>、<a href>、<nav> 和 <table> 自带正确的角色、键盘行为和焦点状态——而这些都得在 <div> 上手动重建,失败往往就发生在重建过程中。
确保每个交互元素都能通过键盘访问,并且焦点清晰可见。 键盘用户依靠焦点环提供的信息进行导航,因此,如果设置了 outline: none 却没有替代方案,他们就只能在页面上盲目移动。
为每个控件提供屏幕阅读器可以朗读的名称。 没有名称的纯图标按钮只会被读作“按钮”,听者只能知道那里有个按钮,却不知道它能做什么。
文本与背景的对比度应达到 4.5:1,UI 边界和焦点指示器的对比度应达到 3:1。 低于这个标准,文本在强光下或廉价显示屏上就难以辨认,对比敏感度降低的人也难以阅读——而最终大多数人都会遇到这种情况。
每个颜色提示都应搭配形状、图标或文字。 对红绿色盲读者来说,单凭红色表示错误没有任何作用;对屏幕阅读器来说,也完全无法传达信息。
页面未发生跳转时,也要播报新出现的内容。 验证错误、保存成功的确认信息和加载完成的结果,如果没有放入实时区域,都会悄无声息。
</critical_requirements>
自动检测: accessibility、a11y、WCAG、ARIA、aria-label、aria-labelledby、aria-describedby、aria-live、aria-expanded、aria-invalid、role 属性、键盘导航、焦点管理、焦点陷阱、focus-visible、跳过链接、landmark、屏幕阅读器、sr-only、颜色对比度、prefers-reduced-motion、tabindex、axe
适用范围:
- 键盘可访问性、Tab 顺序、焦点移动和焦点指示器
- ARIA 角色、状态和属性,以及何时无需使用它们
- 对比度和不依赖颜色传达的信息
- 可访问名称、实时区域和播报时机
- 目标尺寸、动态效果偏好,以及新增这些要求的 WCAG 2.2 标准
其他章节负责:
- 复杂组件的内部实现——经过测试的基础组件库会实现下文所述的 ARIA 约定;本技能说明这些约定,便于你检查所用的组件库。
- 视觉设计令牌——调色板由其他部分设计,本技能提供必须达到的对比度标准。
- 测试运行器机制——运行审计是测试工具的职责;此处说明应断言的内容。
- 内容撰写——阅读难度、通俗语言和字幕文本属于写作决策,而非标记决策。
理念
无障碍界面不是界面的一个变体,而是同一个界面:使用本身就带有含义的元素构建,并明确写出浏览器无法推断的部分。
这样一来,大部分工作就是做减法。语义化 HTML 是无障碍的基准;每个取代真正语义元素的 <div> 都会带来一笔债务,需要通过 ARIA 属性、按键处理程序和焦点管理来偿还,而且往往偿还得并不好。以下属性只适用于没有元素能表达相应含义的情况,并不是叠加到本可承载这些含义的标记上的一层东西。
优先为键盘用户构建界面。指针能完成的操作,键盘也必须能完成;而无需鼠标即可使用的设计,也能用鼠标操作。
<decision_framework>
该场景需要哪种机制
What is being built?
├─ Something that acts when activated → <button>
│ └─ Icon only → add an accessible name
├─ Something that navigates → <a href>
│ └─ Current destination → aria-current="page"
├─ A structural region → <nav>, <main>, <header>, <aside>, <footer>
│ └─ More than one of a kind → label each with aria-label
├─ A complex widget — dialog, listbox, tabs, combobox, tree
│ └─ Take a tested primitive. The ARIA contract is large,
│ and a partial implementation reads as broken rather than absent.
└─ Content that appears without navigation
├─ An error or something urgent → role="alert"
└─ Progress or confirmation → role="status"
是否使用 ARIA: ARIA 的第一条规则就是不要使用 ARIA。在 <div> 上设置 role="button",只能让屏幕阅读器读出正确的名称,却无法提供键盘操作,因此事件处理器、tabindex 和焦点样式仍然缺一不可。只有在没有合适元素能表达相应含义时才使用 ARIA,例如实时区域、披露控件的 aria-expanded,或列标题的排序方向。
AA 还是 AAA: AA 是合规目标,也是法规通常引用的标准。单项 AAA 准则通常实现成本不高,例如 7:1 对比度、44×44 的目标尺寸;在设计允许的情况下值得采用,但不必因此承诺整个产品达到 AAA。
</decision_framework>
核心模式
模式 1:跳过链接
页面上第一个可聚焦的元素,平时隐藏,获得焦点时显示,用于跳过每个页面都会重复出现的导航。
<a href="#main-content" className="skip-link">Skip to main content</a>
…
<main id="main-content" tabIndex={-1}>{children}</main>
目标上的 tabIndex={-1} 能让焦点实际落到该元素上;没有它,片段链接只会移动视口,焦点仍留在文档顶部。
完整代码:examples/core.md
模式 2:地标
地标元素能让屏幕阅读器用户在不同区域间跳转,而不是从头到尾线性阅读。
<header>…</header>
<nav aria-label="Main">…</nav>
<main>…</main>
<aside aria-label="Related">…</aside>
<footer>…</footer>
每个页面只能有一个 <main>。如果两个地标类型相同,就必须分别添加可区分的标签——两个未标注的 <nav> 会被读成完全相同的内容。
完整代码:examples/core.md
模式 3:按钮与链接
触发操作用按钮;导航用链接。这一区别决定了键盘操作方式、读屏播报内容,以及浏览器自带的功能是否可用。
<button onClick={handleSubmit}>Submit form</button>
<a href="/dashboard">Go to dashboard</a>
用来导航的 <button> 会让读屏用户失去“在新标签页中打开”、状态栏网址和鼠标中键点击功能。<div onClick> 则会让他们无法使用整个交互。
完整代码:examples/core.md
模式 4:可访问名称
每个控件都需要名称,只有图标的控件最容易遗漏名称。
<button aria-label="Delete item">
<TrashIcon aria-hidden="true" />
</button>
<button>
<TrashIcon aria-hidden="true" />
<span className="sr-only">Delete item</span>
</button>
两者都会播报“删除项目,按钮”。第二种写法能兼容翻译工具,也能通过文本搜索在 DOM 中找到,因此在这两点重要时应优先采用。
名称解析顺序见 reference.md;完整代码见 examples/screen-reader.md。
模式 5:表单字段与错误
将标签关联到输入框,在输入框上设置无效状态,并确保用户可以从输入框访问错误消息。
<label htmlFor="email">Email</label>
<input
id="email"
type="email"
aria-required="true"
aria-invalid={Boolean(error)}
aria-describedby={error ? "email-error" : undefined}
/>
{error && <span id="email-error" role="alert">{error}</span>}
占位符不能代替标签:用户开始输入时占位符就会消失,而且在大多数设计中的对比度都不达标。
完整代码:examples/forms.md
模式 6:焦点指示器
:focus-visible 会在键盘交互时显示焦点环,鼠标点击时则不显示,这样人们就没有理由再删除焦点样式了。
.button:focus-visible {
outline: 2px solid var(--color-focus);
outline-offset: 2px;
}
至少 2px,且与其背后的内容之间的对比度至少为 3:1。
完整代码:examples/focus.md
模式 7:对话框与焦点移动
无论由谁实现,模态对话框都必须遵守以下约定:打开时焦点移入对话框;对话框打开期间焦点不能移出;按 Escape 键关闭对话框;关闭后焦点返回到打开它的元素。
<div role="dialog" aria-modal="true" aria-labelledby="dialog-title">
<h2 id="dialog-title">{title}</h2>
…
</div>
最常被遗漏的是焦点返回触发元素这一步——如果没有它,键盘用户会回到文档顶部,却不知道自己刚才在哪里。
完整代码:examples/focus.md
模式 8:实时区域
如果内容在没有页面导航的情况下到达,除非通过实时区域播报,否则用户不会听到。
<div role="status">{savedMessage}</div>
<div role="alert">{errorMessage}</div>
role="status" 会等到停顿时再播报;role="alert" 会立即打断当前内容。消息到达前,实时区域必须已经存在于 DOM 中——同时插入区域和内容通常不会触发播报。
模式 9:不依赖颜色传达状态
颜色只是状态信号的点缀,绝不能成为信号本身。
<span className={statusClass}>
<span aria-hidden="true">{status === "error" ? "×" : "✓"}</span>
<span>{status === "error" ? "Failed" : "Complete"}</span>
</span>
图标负责视觉传达,文本负责向屏幕阅读器用户传达;去掉颜色后,两者仍然有效。
完整代码:examples/color.md
模式 10:动态效果偏好
采用主动启用动画的方式,让希望减少动态效果的读者默认获得相应体验。
@media (prefers-reduced-motion: no-preference) {
.card {
animation: slide-in 300ms ease-out;
}
}
reduce 表示尽量减少,而非完全消除——用淡入代替滑动,既能保留反馈,也能消除前庭刺激。
<red_flags>
警示事项
运行时会出问题:
- 使用
outline: none却不提供替代方案——键盘用户会完全失去当前位置;应将焦点环限定为:focus-visible,而不是直接移除。 - 在
<div>或<span>上使用onClick——元素无法获得焦点,也无法通过 Enter 或 Space 激活,且不会被读屏器读出;请使用<button>。 - 在
<div>上设置role="button"——只提供了“按钮”这个词,却没有任何行为,因此仍需自行实现 Tab、Enter 和 Space 的支持。 - 在任何可聚焦元素上设置
aria-hidden="true"——焦点会落在读屏器坚持认为不存在的元素上。 - 将
tabindex设为大于零的值——会让元素排在整个文档顺序之前,而一个这样的值就会打乱整个页面的顺序。 - 输入框没有关联的标签——读屏器只会根据输入框类型播报,因此表单会被读成“编辑文本、编辑文本、编辑文本”。
容易造成意外的行为:
- 对于装饰性图片,正确的值是
alt="";完全省略alt会让读屏器读出文件名。 - 实时区域和其中的消息在同一时刻添加到 DOM 时,通常什么也不会播报——应先渲染容器。
- 实时区域只会播报发生变化的部分,因此“10 项结果中的第 3 项”在原处更新后,可能只会被读成“5”,而没有上下文;
aria-atomic="true"会让读屏器重新播报整个区域。 role="alert"会打断当前播报的内容,因此在其中逐键显示验证消息会导致无法使用。- 占位符不是标签,而且会在读者最需要它时消失。
- 禁用的按钮无法通过焦点访问,因此在表单有效前保持禁用的提交按钮无法告知用户原因。
- 用
<label>包住输入框,以及使用for/htmlFor都可行;但同一个输入框关联两个标签则不行,而且只会播报其中一个。 prefers-reduced-motion: reduce表示减少,而非完全禁止——移除必要的反馈就做得过头了。- 自动化审计只能发现约一半的 WCAG 问题。有关顺序、措辞以及播报是否合理的一切,都需要由人来判断。
</red_flags>