mcpField
W
web-accessibility-web-accessibility

web-accessibility-web-accessibility

github.com/agents-inc设计与 UI26103 次安装2 天前更新
代码仓库

关于 web-accessibility-web-accessibility

这是为构建无障碍 Web 界面提供的实践指南,面向设计和前端开发人员。内容围绕 WCAG 2.2 AA,涵盖语义 HTML、键盘操作、焦点、对比度和辅助技术测试。

主要功能

  1. 01优先使用语义 HTML
  2. 02检查键盘可达性与焦点
  3. 03为控件提供可访问名称
  4. 04满足文本与界面对比度要求
  5. 05处理动态内容通知和颜色信息

适用场景

  1. 01设计可键盘操作的页面
  2. 02为表单字段与错误提供辅助信息
  3. 03实现对话框的焦点管理
  4. 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.yaml
reference.md
examples/
color.md
core.md
focus.md
forms.md
screen-reader.md
tables.md
testing.md
touch-targets.md

详细介绍

无障碍

快速指南: 无障碍的大部分问题,都可以通过选择合适的元素来解决:<button> 自带焦点支持、键盘激活方式和角色,而 <div> 需要十几行代码才能模拟出来。元素无法提供的,是其余这些方面——为每个控件提供名称、显示可见的焦点指示器、确保文本对比度达到 4.5:1、在内容变化时进行播报,以及绝不单靠颜色传达信息。以 WCAG 2.2 AA 级为目标。自动化检查只能发现大约一半的问题,因此键盘检查不可或缺。

详细资源:


<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>

评论

支持 Markdown