better-accessibility

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Accessibility that comes with the craft

融入开发工艺的无障碍设计

Accessibility is not a compliance checkbox bolted on at the end; it is the floor for interface craft. Most of it is free if you use the platform: native elements ship with keyboard support, real labels announce themselves, and a visible focus ring is one CSS rule. Apply these principles when building or reviewing UI code, and match the project's existing styling system (Tailwind vs. plain CSS vs. CSS-in-JS) when applying fixes.
When reviewing, walk the interface as a keyboard-only user first (every flow must complete without a mouse), then as a screen-reader user: does each control announce a name, a role, and its state? When unsure, prefer the platform default over a custom rebuild, and remove ARIA rather than add it.
Contrast math (APCA thresholds, fixing contrast in OKLCH) is covered by the
better-colors
skill; text sizes, iOS input zoom, and RTL are covered by the
better-typography
skill.
无障碍设计不是项目末期才匆匆补上的合规检查项,而是界面开发工艺的基础要求。只要合理利用原生平台能力,大部分无障碍特性都能轻松实现:原生元素自带键盘支持、真实标签会自动播报、只需一行CSS规则就能添加可见焦点环。在构建或审查UI代码时遵循这些原则,修复问题时要匹配项目现有的样式系统(Tailwind、纯CSS或CSS-in-JS)。
审查时,先以纯键盘用户的视角遍历界面(所有流程都必须无需鼠标即可完成),再切换到屏幕阅读器用户视角:每个控件是否会播报名称、角色和状态?不确定时,优先使用平台默认实现而非自定义重构,能移除ARIA就不要添加。
对比度计算(APCA阈值、OKLCH对比度修复)由
better-colors
技能覆盖;文本尺寸、iOS输入缩放和RTL布局由
better-typography
技能覆盖。

Quick Reference

快速参考

CategoryWhen to Use
Focus & KeyboardFocus rings, skip links, tabindex, focus trapping, APG keyboard patterns
Semantics & ARIANative elements first, button vs link, landmarks, accessible names, disabled states
FormsLabels, autocomplete, error messaging, input types
Screen ReadersVisually hidden content, live regions, toasts, alt text, SVG
Hit AreasTarget sizes, expanding hit areas, collision rules
Motion & Zoom
prefers-reduced-motion
, autoplay and timed UI, 200% zoom, reflow, rem vs px
分类使用场景
焦点与键盘焦点环、跳转链接、tabindex、焦点捕获、APCA键盘交互模式
语义化与ARIA优先使用原生元素、按钮与链接的区别、地标区域、无障碍名称、禁用状态
表单标签、自动填充、错误提示、输入类型
屏幕阅读器视觉隐藏内容、实时区域、提示框、替代文本、SVG
点击区域目标尺寸、扩展点击区域、碰撞规则
动效与缩放
prefers-reduced-motion
、自动播放与定时UI、200%缩放、回流、rem与px对比

Core Principles

核心原则

1. Native Elements First

1. 优先使用原生元素

The first rule of ARIA: don't use ARIA when a native element exists.
<button>
for actions,
<a href>
for navigation (it must support Cmd/Ctrl/middle-click), never
<div onClick>
. No ARIA is better than bad ARIA.
ARIA的第一条规则:有原生元素可用时,不要使用ARIA。用
<button>
实现操作,用
<a href>
实现导航(必须支持Cmd/Ctrl/中键点击),绝对不要用
<div onClick>
。没有ARIA比错误使用ARIA更好。

2. Visible Focus Rings

2. 可见焦点环

Style
:focus-visible
, not bare
:focus
, so keyboard users get a ring and mouse users don't. The browser's default ring (
outline-style: auto
) renders the focus color the user configured in their OS and browser; prefer keeping it and only adding
outline-offset: 2px
. If the design needs a custom ring, use
outline: 2px solid
with no color (it renders
currentColor
, never a hardcoded brand color) at
3:1
contrast against adjacent colors. Never
outline: none
without a visible replacement.
样式化
:focus-visible
而非裸用
:focus
,这样键盘用户能看到焦点环,鼠标用户则不会。浏览器默认焦点环(
outline-style: auto
)会渲染用户在系统和浏览器中配置的焦点颜色;建议保留默认样式,仅添加
outline-offset: 2px
。如果设计需要自定义焦点环,使用
outline: 2px solid
且不指定颜色(会渲染
currentColor
,绝不要硬编码品牌色),且与相邻颜色的对比度需达到3:1。绝对不要在没有可见替代方案的情况下使用
outline: none

3. Full Keyboard Support

3. 完整键盘支持

Every pointer interaction needs a keyboard path, following the ARIA APG patterns: Escape closes overlays, arrow keys move within composite widgets (tabs, menus, listboxes), Tab moves between widgets, Enter and Space activate. Only
tabindex="0"
(join the natural tab order) and
tabindex="-1"
(programmatic focus), never positive values, which break the natural order. Composite widgets use roving tabindex: the active item is
0
, all others
-1
.
所有指针交互都必须有对应的键盘操作路径,遵循ARIA APG模式:Esc键关闭弹窗、箭头键在复合组件(标签页、菜单、列表框)内移动、Tab键在组件间切换、Enter和Space键触发操作。仅使用
tabindex="0"
(加入自然Tab顺序)和
tabindex="-1"
(程序化聚焦),绝对不要使用正值,否则会破坏自然顺序。复合组件使用漫游tabindex:当前激活项设为
0
,其余项设为
-1

4. Trap and Restore Focus

4. 捕获并恢复焦点

Modals set
inert
on the background content, move focus inside on open, and return focus to the trigger on close. Add
overscroll-behavior: contain
so background content doesn't scroll.
模态框需为背景内容设置
inert
属性,打开时将焦点移至框内,关闭时将焦点返回至触发按钮。添加
overscroll-behavior: contain
防止背景内容滚动。

5. Minimum Hit Area

5. 最小点击区域

Interactive elements need a 44×44px hit area for touch or mobile contexts, at least 40×40px on desktop; WCAG 2.5.8's hard floor is 24×24px. Extend with a pseudo-element if the visible element is smaller. Never let hit areas of two elements overlap.
交互元素在触摸或移动场景下需要44×44px的点击区域,桌面端至少40×40px;WCAG 2.5.8的硬性标准是24×24px。如果可见元素尺寸更小,可通过伪元素扩展点击区域。绝对不要让两个元素的点击区域重叠。

6. Label and Type Every Control

6. 为每个控件添加标签和类型

Every input gets a
<label for>
or wrapping
<label>
; a placeholder is never a label, and label and control share one hit target: no dead zones between a checkbox and its text. Add
autocomplete
with a meaningful
name
, and the correct
type
and
inputmode
for the keyboard. Never block paste; users paste passwords and one-time codes.
每个输入框都要有
<label for>
或包裹式
<label>
;占位符永远不能替代标签,且标签与控件需共享同一个点击目标:复选框与其文本之间不能有无效区域。添加带有效
name
autocomplete
属性,并为键盘设置正确的
type
inputmode
。绝对不要阻止粘贴操作;用户常会粘贴密码和一次性验证码。

7. Errors That Announce

7. 可播报的错误提示

Keep submit enabled until the request starts, then disable with a spinner while keeping the original label. Validate on submit: mark failing fields with
aria-invalid="true"
, point
aria-describedby
at the inline error text, and focus the first invalid field. Use native
disabled
when a native control is genuinely unavailable. Use
aria-disabled="true"
only when retaining focusability or discoverability is intentional; then block pointer, keyboard, and form behavior in code and style the state explicitly.
提交按钮保持启用状态直到请求开始,之后禁用并添加加载动画,但保留原标签。在提交时验证:为失败字段添加
aria-invalid="true"
,将
aria-describedby
指向行内错误文本,并聚焦第一个无效字段。当原生控件确实不可用时使用原生
disabled
属性。仅当需要保留可聚焦性或可发现性时才使用
aria-disabled="true"
;此时需在代码中阻止指针、键盘和表单行为,并显式样式化该状态。

8. Accessible Names Everywhere

8. 随处可见的无障碍名称

Icon-only buttons need a descriptive
aria-label
. Visible label text must appear in the accessible name. Decorative elements get
aria-hidden="true"
, never on a focusable element.
仅含图标的按钮需要描述性的
aria-label
。可见标签文本必须包含在无障碍名称中。装饰性元素添加
aria-hidden="true"
,绝对不要在可聚焦元素上使用该属性。

9. Don't Rely on Color Alone

9. 不要仅依赖颜色

Status needs a redundant cue: icon, text, or underline alongside the color. Contrast floors:
4.5:1
for text,
3:1
for UI components and focus indicators (the WCAG 2 minimums;
better-colors
prefers APCA thresholds). When contrast fails, report the failing pair and the threshold it misses; don't change the project's colors unless asked, as the
better-colors
skill covers measuring and, on request, fixing.
状态提示需要冗余线索:除颜色外,还要搭配图标、文本或下划线。对比度标准:文本对比度需达到4.5:1,UI组件和焦点指示器需达到3:1(WCAG 2的最低要求;
better-colors
技能更推荐APCA阈值)。当对比度不达标时,报告不达标颜色对及所需阈值;除非被要求,否则不要修改项目颜色,因为
better-colors
技能负责测量并按需修复。

10. Honor prefers-reduced-motion

10. 尊重prefers-reduced-motion

Wrap motion in
@media (prefers-reduced-motion: no-preference)
so it is opt-in. Under reduced motion, replace slides and scales with opacity crossfades; kill parallax and autoplay entirely. Independent of the preference: autoplaying media needs a visible pause control, and toasts carrying actions or errors stay until dismissed.
将动效包裹在
@media (prefers-reduced-motion: no-preference)
中,让用户主动选择开启动效。在减少动效模式下,将滑动和缩放替换为透明度淡入淡出;完全禁用视差和自动播放。与用户偏好无关的规则:自动播放的媒体需要可见的暂停控件,带有操作或错误信息的提示框需保持显示直到被关闭。

11. Announce Dynamic Content

11. 播报动态内容

Toasts and inline validation use
aria-live="polite"
(
role="status"
); reserve
assertive
(
role="alert"
) for errors. The live region must exist empty in the DOM before its content is injected, or it won't announce.
提示框和行内验证使用
aria-live="polite"
role="status"
);仅在错误场景下使用
assertive
role="alert"
)。实时区域必须在内容注入前就已空状态存在于DOM中,否则无法播报。

12. Alt Text by Purpose

12. 按需设置替代文本

Decorative images get
alt=""
, informative images describe the meaning, functional images describe the action: a search icon button is
alt="Search"
, not
alt="magnifying glass"
.
装饰性图片设置
alt=""
,信息性图片描述其含义,功能性图片描述其操作:搜索图标按钮的替代文本应为
alt="Search"
,而非
alt="放大镜"

13. Structure Is Navigation

13. 结构即导航

One
<h1>
per page, no skipped heading levels, exactly one
<main>
. A "Skip to content" link is the first focusable element, and anchored headings get
scroll-margin-top
.
每页一个
<h1>
,不要跳过标题层级,且仅有一个
<main>
。“跳转到内容”链接应为第一个可聚焦元素,锚定标题需添加
scroll-margin-top

14. Survive Zoom and Text Resize

14. 适配缩放与文本调整

The page must work at 200% zoom and reflow at 320px width without horizontal scrolling. Use
min-height
instead of fixed
height
on text containers, prefer
rem
breakpoints where they fit the codebase's conventions, and never use
user-scalable=no
or
maximum-scale=1
.
页面必须支持200%缩放,且在320px宽度下可回流而无横向滚动。文本容器使用
min-height
而非固定
height
,根据代码库约定优先使用
rem
作为断点单位,绝对不要使用
user-scalable=no
maximum-scale=1

Common Mistakes

常见错误

MistakeFix
outline: none
to remove the focus ring
Style
:focus-visible
instead; mouse clicks won't show it
Hardcoded brand color on focus ringsKeep the default ring (
outline-style: auto
) or use colorless
outline: 2px solid
(
currentColor
)
<div onClick>
for a button or link
<button>
for actions,
<a href>
for navigation
Placeholder used as the only labelAdd a visible
<label for>
; placeholders disappear on input
Positive
tabindex
to fix focus order
Fix the DOM order; only use
0
and
-1
Live region injected together with its messagePre-render the empty region, inject text afterwards
assertive
live region for a routine toast
Use
polite
; reserve
assertive
for errors
aria-hidden="true"
on a focusable element
Remove it or make the element non-focusable
Functional icon alt describes the pictureDescribe the action:
alt="Search"
, not
alt="magnifying glass"
maximum-scale=1
to stop iOS input zoom
16px input font on mobile (see
better-typography
); never block zoom
Submit disabled until the form is validKeep it enabled; validate on submit and focus the first error
错误修复方案
使用
outline: none
移除焦点环
改为样式化
:focus-visible
;鼠标点击时不会显示焦点环
焦点环硬编码品牌色保留默认焦点环(
outline-style: auto
)或使用无颜色的
outline: 2px solid
currentColor
<div onClick>
实现按钮或链接
<button>
实现操作,用
<a href>
实现导航
仅用占位符作为标签添加可见的
<label for>
;占位符在输入时会消失
使用正值
tabindex
修复焦点顺序
修复DOM顺序;仅使用
0
-1
实时区域与消息一同注入DOM预渲染空的实时区域,之后再注入文本
常规提示框使用
assertive
实时区域
使用
polite
;仅在错误场景下使用
assertive
在可聚焦元素上使用
aria-hidden="true"
移除该属性或让元素不可聚焦
功能性图标替代文本描述图片内容描述操作:
alt="Search"
,而非
alt="放大镜"
使用
maximum-scale=1
阻止iOS输入缩放
移动端输入框字体设为16px(详见
better-typography
);绝对不要阻止缩放
表单验证通过前禁用提交按钮保持提交按钮启用;提交失败时聚焦第一个错误字段

Review Output Format

审查输出格式

Present every review in two parts.
所有审查结果分为两部分呈现。

Findings

问题发现

Group findings by principle. Use a markdown table with Severity, Location, Before, After, and Why columns. Include every change made or proposed, not a subset. Never use separate "Before:" / "After:" lines.
  • Severity:
    HIGH
    prevents a task, hides content from assistive technology, or creates a systemic accessibility failure;
    MEDIUM
    makes an interaction meaningfully harder;
    LOW
    is isolated polish.
  • Location: cite
    path/to/file:line
    . If the artifact has no source files, cite the exact screen and component instead.
  • Before / After: show the current implementation and an actionable replacement.
  • Why: name the violated principle and its user impact.
Consolidate a repeated systemic issue into one row and list every affected location. Omit principles with no findings.
按原则分组展示问题。使用包含严重程度位置修改前修改后原因列的Markdown表格。列出所有已做或建议的修改,不要遗漏。不要使用单独的“修改前:”/“修改后:”行。
  • 严重程度
    HIGH
    表示阻碍任务完成、辅助技术无法识别内容或存在系统性无障碍缺陷;
    MEDIUM
    表示交互难度显著增加;
    LOW
    表示孤立的优化点。
  • 位置:引用
    path/to/file:line
    。如果没有源文件,则引用具体屏幕和组件。
  • 修改前/修改后:展示当前实现和可执行的替代方案。
  • 原因:指出违反的原则及其对用户的影响。
将重复的系统性问题合并为一行,并列出所有受影响的位置。没有问题的原则可省略。

Example

示例

Accessible names everywhere

随处可见的无障碍名称

SeverityLocationBeforeAfterWhy
HIGH
src/Dialog.tsx:42
<button><XIcon /></button>
Add
aria-label="Close"
; mark the icon
aria-hidden="true"
The icon-only control has no accessible name
HIGH
src/Nav.tsx:18
<a href="/settings"><GearIcon /></a>
Add
aria-label="Settings"
The link destination is unavailable to screen readers
严重程度位置修改前修改后原因
HIGH
src/Dialog.tsx:42
<button><XIcon /></button>
添加
aria-label="Close"
;为图标设置
aria-hidden="true"
仅含图标的控件无无障碍名称
HIGH
src/Nav.tsx:18
<a href="/settings"><GearIcon /></a>
添加
aria-label="Settings"
屏幕阅读器无法识别链接目标

Visible focus rings

可见焦点环

SeverityLocationBeforeAfterWhy
HIGH
src/button.css:12
button:focus { outline: none; }
button:focus-visible { outline: 2px solid; outline-offset: 2px; }
Keyboard users cannot see focus
HIGH
src/Menu.tsx:31
focus:outline-none
focus-visible:outline-2 focus-visible:outline-offset-2
Menu navigation has no visible focus indicator
严重程度位置修改前修改后原因
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
菜单导航无可见焦点指示器

Errors that announce

可播报的错误提示

SeverityLocationBeforeAfterWhy
HIGH
src/EmailField.tsx:27
Error shown only as
border-red-500
Add
aria-invalid="true"
+
aria-describedby="email-error"
with inline error text
Color alone neither explains nor announces the error
MEDIUM
src/SignupForm.tsx:64
Submit disabled until the form is validKeep submit enabled; on failure, focus the first invalid fieldA disabled action hides what must be fixed
严重程度位置修改前修改后原因
HIGH
src/EmailField.tsx:27
仅用
border-red-500
显示错误
添加
aria-invalid="true"
+
aria-describedby="email-error"
并关联行内错误文本
仅靠颜色既无法解释也无法播报错误
MEDIUM
src/SignupForm.tsx:64
表单验证通过前禁用提交按钮保持提交按钮启用;提交失败时聚焦第一个错误字段禁用操作会隐藏需要修复的问题

Minimum hit area

最小点击区域

SeverityLocationBeforeAfterWhy
MEDIUM
src/Toolbar.tsx:22
size-4
icon-only button
Extend the hit area to 44×44px with
after:absolute after:size-11
The target is too small for reliable touch input
严重程度位置修改前修改后原因
MEDIUM
src/Toolbar.tsx:22
size-4
仅含图标的按钮
after:absolute after:size-11
将点击区域扩展至44×44px
目标尺寸过小,无法可靠进行触摸输入

Verification and Verdict

验证与结论

After the findings:
  1. Verification: list the exact checks run and their observed results, including keyboard traversal, accessible-name inspection, and screen-reader or automated checks when applicable. If a check was not run, state what still needs verification.
  2. Verdict:
    Block
    if any
    HIGH
    finding remains,
    Needs changes
    if only
    MEDIUM
    or
    LOW
    findings remain, and
    Approve
    only when no actionable findings remain.
When there are no findings, omit the tables, state "No actionable accessibility findings", report verification, and end with
Approve
.
问题发现之后:
  1. 验证:列出执行的具体检查及其结果,包括键盘遍历、无障碍名称检查、屏幕阅读器或自动化检查(如适用)。如果未执行某项检查,说明仍需验证的内容。
  2. 结论:若存在未解决的
    HIGH
    级问题,标记为
    Block
    ;若仅存在
    MEDIUM
    LOW
    级问题,标记为
    Needs changes
    ;仅当无待解决问题时,标记为
    Approve
如果没有问题,省略表格,说明“无待解决的无障碍问题”,报告验证情况,最后标记为
Approve