
better-accessibility
热门适用于产品界面的无障碍工程规范(Accessibility),涵盖焦点状态、键盘导航支持、ARIA 属性、表单交互及屏幕阅读器适配。当构建或审查 UI 组件、弹窗(modals)、菜单、表单、自定义控件,或者用户提出“做一下无障碍适配/无障碍优化”、反馈键盘导航或屏幕阅读器问题时使用。触发词包含 accessibility, a11y, WCAG, aria, focus ring, focus-visible, focus trap, keyboard navigation, tab order, tabindex, screen reader, sr-only, aria-live, alt text, hit area, touch target, prefers-reduced-motion, autoplay, toast duration, skip link, semantic HTML, aria-label, form errors, disabled buttons, "not keyboard accessible".
适用于产品界面的无障碍工程规范(Accessibility),涵盖焦点状态、键盘导航支持、ARIA 属性、表单交互及屏幕阅读器适配。当构建或审查 UI 组件、弹窗(modals)、菜单、表单、自定义控件,或者用户提出“做一下无障碍适配/无障碍优化”、反馈键盘导航或屏幕阅读器问题时使用。触发词包含 accessibility, a11y, WCAG, aria, focus ring, focus-visible, focus trap, keyboard navigation, tab order, tabindex, screen reader, sr-only, aria-live, alt text, hit area, touch target, prefers-reduced-motion, autoplay, toast duration, skip link, semantic HTML, aria-label, form errors, disabled buttons, "not keyboard accessible".
把无障碍做进 UI 手艺里(Interface Craft)
无障碍(Accessibility / a11y)不是最后阶段为了搞定合规才硬塞进来的勾选框,而是界面设计与工程手艺的底线。只要善用 Web 原生平台特性,绝大多数无障碍支持都是“免费”的:原生 HTML 元素自带键盘支持、真正的 Label 会自动向读屏软件朗读,而一个清晰可见的焦点环(Focus Ring)不过就是一行 CSS 的事。在编写或审查 UI 代码时,请贯彻以下原则;修复问题时,注意保持与项目现有的样式方案(Tailwind / 原生 CSS / CSS-in-JS)一致。
在做 UI 审查时,先以“纯键盘用户”的身份把整个界面走一遍(所有业务流程必须在不用鼠标的情况下顺利完成);然后再以“屏幕阅读器用户”的身份测试:每个控件是否都能清晰朗读出它的名称(Name)、角色(Role)和状态(State)?当不确定怎么选时,优先使用 Web 平台默认的元素而非手造控件;能不用 ARIA 就尽量不用,少加比乱加强。
渲染后的文本/背景对比度测量与颜色修复,请参考 better-colors Skill;视觉字号与 iOS 输入框缩放问题,请参考 better-typography;RTL 布局与空间布局,请参考 better-layout。
快速参考
| 分类 | 适用场景 |
|---|---|
| 焦点与键盘导航 | 焦点环、跳过链接(Skip link)、tabindex、焦点捕获/锁死(Focus trapping)、APG 键盘交互模式 |
| 语义化与 ARIA | 原生元素优先、Button 与 Link 的区分、地标元素(Landmarks)、可访问名称(Accessible names)、禁用状态 |
| 表单 | Label 标签、自动补全(autocomplete)、错误提示、输入框类型 |
| 屏幕阅读器 | 视觉隐藏内容、动态区域(Live regions)、Toast 提示、Alt 文本、SVG 适配 |
| 点击/热区面积 | 目标尺寸、扩大热区、碰撞规则 |
| 动画与缩放 | prefers-reduced-motion、自动播放与定时 UI、200% 页面缩放、流式重排(Reflow)、rem 与 px 选用 |
核心原则
1. 原生元素优先 (Native Elements First)
ARIA 的第一条法则:只要有原生 HTML 元素能用,就绝不要用 ARIA。按钮操作用 <button>,页面跳转用 <a href>(必须支持 Cmd/Ctrl/中键点击新标签页打开),严禁写 <div onClick>。没有 ARIA 远比写错 ARIA 强。
2. 焦点环清晰可见 (Visible Focus Rings)
针对 :focus-visible 样式化,而不是直接给 :focus 设样式,这样键盘操作时能看到焦点环,而鼠标点击时不会出现无谓的边框。优先使用浏览器默认未修改的焦点指示器。如果设计规范要求自定义焦点环,请使用项目统一的 focus token 或显式指定的颜色,并确保焦点指示器在其经过的每一种相邻背景色上都能看清;仅在确认对比度无误后方可使用 currentColor。焦点环应至少保持 2px 实线外边框或同等的显眼面积。切勿在没有替代方案的情况下写 outline: none,且在强制颜色模式(Forced-colors mode / 高对比度模式)下需保留系统配色。
3. 完整的键盘支持 (Full Keyboard Support)
每一个指针(鼠标/触控)交互都必须有对应的键盘操作路径,遵循 ARIA APG 设计模式:按 Escape 键关闭浮层/弹窗,方向键在复合控件内部移动(标签页 Tabs、菜单 Menus、列表框 Listboxes),Tab 键在不同控件间切换,Enter 和 Space 键触发激活。只允许使用 tabindex="0"(加入自然 Tab 焦点顺序)和 tabindex="-1"(仅支持代码编程聚焦),绝不要使用正数 tabindex(如 tabindex="1"),这会破坏自然的焦点流。复合控件需采用巡回焦点模式(Roving tabindex):当前激活项为 0,其余全为 -1。
4. 捕获与恢复焦点 (Trap and Restore Focus)
模态弹窗(Modals)打开时,需为背景内容设置 inert 属性,并将焦点移入弹窗内部;弹窗关闭时,必须将焦点还给最初的触发按钮。添加 overscroll-behavior: contain 样式,防止滚动弹窗时连带背景一起滚动。
5. 最小热区面积 (Minimum Hit Area)
WCAG 2.5.8 AA 级的基线标准是 24×24 CSS 像素的目标尺寸,或符合其规定的间距、等效控件、内联文本、浏览器原生控件或必要例外。为了更轻松地点击,在密度允许的情况下,移动端/触控场景建议达到 44×44px,桌面端界面建议达到 40×40px。如果视觉上控件需要保持较小尺寸,可通过伪元素(如 ::after)扩展热区。切勿让扩展后的热区相互重叠。
6. 每一个控件都要有 Label 和正确的 Type
每个输入框都要配备 <label for> 或被 <label> 包裹;Placeholder(占位符)绝对不能当作 Label 使用;Label 和控件应共享同一个热区——复选框与其旁边文本之间不能有点击无效的死角。为输入框添加具有实际语义 name 的 autocomplete 属性,以及匹配键盘类型的 type 和 inputmode。切勿禁止粘贴;用户需要粘贴密码和一次性验证码。
7. 会主动朗读的错误提示 (Errors That Announce)
在提交请求发起之前,保持“提交”按钮处于可点击状态;发起请求后将其禁用并显示 Loading 圈,但要保留原本的文字 Label。在提交时进行表单校验:给校验失败的字段标注 aria-invalid="true",将 aria-describedby 指向内联的错误文案,并将焦点移至第一个不合格的输入框。仅在原生控件真正不可用时才使用原生 disabled。只有在刻意保留可聚焦性或可发现性时才使用 aria-disabled="true";此时须在代码中手动阻止指针、键盘和表单行为,并显式设置该状态的样式。
8. 随处可见的可访问名称 (Accessible Names Everywhere)
纯图标按钮必须配备描述性的 aria-label。界面上可见的 Label 文本必须包含在无障碍名称(Accessible Name)中。装饰性元素需加上 aria-hidden="true",但绝不能加在可聚焦的元素上。
9. 切勿单纯依赖颜色传达信息 (Don't Rely on Color Alone)
状态提示需要颜色以外的冗余视觉线索:比如配合图标、文字或下划线。根据内容和状态确定适用的 WCAG 对比度要求,然后使用 better-colors 测量渲染后的前景色/背景色对。当对比度不达标时,报告该颜色对及未达到的标准;除非用户明确要求,否则不要随意更改项目的配色方案。
10. 尊重 prefers-reduced-motion
将动画效果包裹在 @media (prefers-reduced-motion: no-preference) 中,使其成为渐进增强的选择。在开启“减弱动态效果”模式下,将位移/缩放动画替换为透明度渐变(Opacity Crossfade);彻底禁用视差滚动(Parallax)和视频自动播放。无论用户是否有此偏好:自动播放的媒体必须提供显式的暂停控件;带有操作按钮或错误信息的 Toast 提示必须保持显示,直到用户主动关闭。
11. 动态内容的朗读播报 (Announce Dynamic Content)
字段专属的校验提示使用 aria-describedby;与具体控件无关的非紧急更新(如 Toast 弹窗、搜索结果数量通知),使用温和的 Live Region(role="status");只有与控件无关的紧急错误才使用 role="alert"。为了实现可靠的重复温和播报,请在更新文本之前先渲染一个稳定的空区域;动态插入的 alert 在不同设备上支持度不同,必须在目标屏幕阅读器上进行实测。
12. 按用途撰写 Alt 文本 (Alt Text by Purpose)
装饰性图片使用 alt="";传达信息的图片描述其内在含义;功能性图片描述其对应的操作:比如搜索图标按钮的 Alt 应为 alt="搜索",而不是 alt="放大镜"。
13. 良好结构即是高效导航 (Structure Is Navigation)
使用能准确描述对应章节并形成清晰大纲的标题;推荐的默认做法是单页面设置一个 <h1> 并按层级正确嵌套,但这并非 WCAG 的硬性通过/不通过规则。在页面中暴露一个可见的主内容区 <main> 地标。如果主内容前面有重复的导航或 Header,请将“跳过至主内容”(Skip to content)链接设为页面上的第一个可聚焦元素。带锚点的标题需设置 scroll-margin-top。
14. 完美适配缩放与字号调整 (Survive Zoom and Text Resize)
页面在 200% 放大倍率以及 320px 宽度下必须能正常工作且实现流式重排,不得出现横向滚动条。在文本容器上使用 min-height 而不是固定的 height;在符合项目代码规范的前提下优先使用 rem 响应式断点;严禁写 user-scalable=no 或 maximum-scale=1。
常见误区与修复方式
| 常见误区 | 正确修复方案 |
|---|---|
使用 outline: none 直接干掉焦点环 |
给 :focus-visible 设置样式;这样鼠标点击时不会显示焦点环,键盘操作时会显示 |
| 误以为自定义焦点颜色在任何背景下都能看清 | 在所有相邻背景色及强制颜色模式(高对比度模式)下验证完整的指示器对比度 |
使用 <div onClick> 充当按钮或链接 |
操作用 <button>,页面跳转用 <a href> |
| 仅把 Placeholder(占位符)当作唯一的 Label | 添加可见的 <label for>;用户输入内容后 Placeholder 会消失 |
使用正数 tabindex 来调整焦点顺序 |
修正 DOM 树中的节点顺序;tabindex 只使用 0 和 -1 |
| 重复的温和更新提示播报不稳定/时灵时不灵 | 保持一个稳定的空 status 区域并更新其文本内容;在目标屏幕阅读器上实测 |
给常规 Toast 提示使用 assertive 级别的动态区域 |
使用 polite;assertive 仅留给紧急错误提示 |
在可聚焦的元素上写了 aria-hidden="true" |
移除该属性,或者使该元素不可聚焦 |
| 功能性图标的 Alt 文本在描述图片长相 | 描述其功能操作:用 alt="搜索",而不是 alt="放大镜" |
使用 maximum-scale=1 来阻止 iOS 输入框自动放大 |
将移动端输入框字号设为至少 16px(参考 better-typography);绝不禁止用户缩放 |
| 表单未填完前禁用提交按钮 | 保持提交按钮可点击;在提交时触发校验并聚焦至第一个错误字段 |
审查输出格式 (Review Output Format)
仅当用户明确要求提供独立的无障碍审查报告时,才使用此格式。当由 better-interface 统筹审查时,请将本领域的证据与发现提交给该 Skill,由其输出格式、严重级标准、归并规则、上限及最终结论决定。
独立无障碍审查报告分为两个部分展示。
审查发现 (Findings)
按原则将所有已确认的问题分组。使用包含 Severity(严重程度)、Location(位置)、Before(修改前)、After(修改后)、Why(原因)列的 Markdown 表格。切勿拆分成多行“Before:”/“After:”。
- Severity:
HIGH(阻碍用户完成任务、对辅助技术隐藏内容、或造成系统性无障碍失效);MEDIUM(显著增加交互难度);LOW(局部细节打磨)。 - Location: 引用
path/to/file:line。如果产物没有源码文件,则引用具体的页面和组件名称。 - Before / After: 展示当前实现以及可执行的替换方案。
- Why: 说明违反的原则及其对用户体验造成的影响。
将重复出现的系统性问题合并为一行,并列出所有受影响的位置。无问题的原则直接省略。
示例
随处可见的可访问名称
| Severity | Location | Before | After | Why |
|---|---|---|---|---|
| HIGH | src/Dialog.tsx:42 |
<button><XIcon /></button> |
添加 aria-label="关闭"; 图标加上 aria-hidden="true" |
纯图标控件缺失可访问名称 |
| HIGH | src/Nav.tsx:18 |
<a href="/settings"><GearIcon /></a> |
添加 aria-label="设置" |
屏幕阅读器无法获知链接跳转目标 |
焦点环清晰可见
| Severity | Location | Before | After | Why |
|---|---|---|---|---|
| HIGH | src/button.css:12 |
button:focus { outline: none; } |
button:focus-visible { outline: 2px solid; outline-offset: 2px; } |
键盘用户无法看到当前焦点位置 |
| HIGH | src/Menu.tsx:31 |
focus:outline-none |
focus-visible:outline-2 focus-visible:outline-offset-2 |
菜单键盘导航缺乏可见的焦点指示器 |
会主动朗读的错误提示
| Severity | Location | Before | After | Why |
|---|---|---|---|---|
| HIGH | src/EmailField.tsx:27 |
仅靠 border-red-500 标红显示错误 |
添加 aria-invalid="true" + aria-describedby="email-error" 关联内联错误文本 |
仅靠颜色既无法解释错误,也无法自动朗读 |
| MEDIUM | src/SignupForm.tsx:64 |
表单合规前禁用提交按钮 | 保持提交按钮可点击;失败时聚焦第一个无效字段 | 禁用按钮会隐藏需要修正的内容 |
最小热区面积
| Severity | Location | Before | After | Why |
|---|---|---|---|---|
| MEDIUM | src/Toolbar.tsx:22 |
size-4 纯图标按钮 |
使用 after:absolute after:size-11 将热区扩展至 44×44px |
目标尺寸太小,高频触控容易误操作 |
验证与结论 (Verification and Verdict)
在审查发现之后:
- 验证 (Verification):列出具体执行的检查项及其观测结果,包括键盘遍历测试、可访问名称检查,以及适用的屏幕阅读器或自动化工具检查。若某项检查未执行,请注明仍需验证的内容。
- 结论 (Verdict):





