用 Claude Code 落地无障碍:语义 HTML、ARIA、axe 与人工检查
用 Claude Code 做无障碍改造:语义 HTML、键盘、表单、焦点、axe 和读屏检查。
无障碍不是发布前跑一次工具就结束的任务。它从语义 HTML 开始,延伸到键盘操作、焦点管理、表单错误、颜色对比,再到自动化检查和读屏器人工确认。
Claude Code 可以加速这条流程,但前提是提示词要足够具体。只写“帮我改成无障碍”很容易得到危险结果:给 div 加了 ARIA,却没有键盘事件;弹窗看起来正常,但焦点会跑到背景;表单有红色错误文案,但读屏器听不到。
本文使用官方资料作为基准:W3C WCAG 2.2 作为实务目标,WAI-ARIA Authoring Practices Guide 作为组件模式参考,MDN ARIA 指南 解释为什么要优先语义 HTML,Deque axe-core 文档 用于自动化检查,Claude Code 官方文档 用于确认工具基础。
先定义合格线
团队里最危险的说法是“让它 accessible 一点”。Claude Code 会猜测范围,而猜测通常会带来过大的 diff。更稳的目标是:以 WCAG 2.2 AA 为实际目标,优先使用语义 HTML,只在必要时补 ARIA,核心路径必须可用键盘操作,并留下自动检查和人工检查记录。
| 领域 | 合格线 | 常见失败 |
|---|---|---|
| 语义 HTML | button、a、form、label、main、nav 用在正确位置 | 用可点击 div 代替原生控件 |
| 键盘 | Tab、Shift+Tab、Enter、Space、Escape 能完成主要流程 | 弹窗只能鼠标关闭 |
| 焦点 | 打开 UI 后焦点进入,关闭后回到触发按钮 | 焦点跑到背景内容 |
| ARIA | 原生 HTML 表达不了状态时才添加 | 用 aria-label 掩盖缺少可见标签的问题 |
| 颜色 | 文本、按钮、错误、焦点状态都有足够对比 | 只用浅灰或红色表达状态 |
| 表单 | 标签、说明、必填、错误都和输入框关联 | 错误可见但不会被读屏器宣布 |
| 测试 | axe 自动检查加键盘和读屏器手动检查 | 自动检查 0 条就认为完成 |
MDN 对 ARIA 的建议很直接:如果原生 HTML 元素已经提供了需要的语义和行为,就优先用原生元素。也就是说,Claude Code 的第一步应该是修正 HTML 结构,而不是马上堆 ARIA。
给 Claude Code 的安全提示词
无障碍提示词要像审查清单,而不是一句愿望。范围、禁止修改的内容、目标标准、验证证据都要写进去。
claude <<'PROMPT'
Scope:
- Review only src/components/CheckoutForm.tsx and its tests.
- Do not change pricing copy, analytics events, or unrelated styles.
Accessibility target:
- Use WCAG 2.2 AA as the practical target.
- Prefer semantic HTML before ARIA.
- Add ARIA only when native HTML cannot express the state.
Check these items:
- Labels, descriptions, required state, and validation errors.
- Keyboard operation with Tab, Shift+Tab, Enter, Space, and Escape.
- Focus order, visible focus, and focus return after closing UI.
- Color contrast and non-color error indicators.
- Automated axe check plus manual screen-reader notes.
Output:
- Findings first, with file and line references.
- Minimal patch.
- Commands to verify.
- Any remaining risk.
PROMPT
“Findings first” 很重要。先让 Claude Code 说清楚问题,再让它改代码,review 时就能判断差分是否必要。这个习惯也适用于内容站、商品页和咨询表单,因为这些地方往往包含 CTA、统计事件和收入路径。相关的范围控制可以继续看 Claude Code 效率技巧。
用例一:商品 CTA 和文章 CTA
第一个真实场景是商品页或文章底部 CTA。它看起来像卡片,但行为通常是跳转,因此应该使用真实链接,而不是给整个 div 绑点击事件。
<div class="hero-card" onclick="location.href='/zh/products'">
<div class="title">Claude Code Templates</div>
<div class="button">Buy now</div>
</div>
更稳的写法是恢复结构:区域有标题,说明讲清楚收益,跳转使用 a。
<section aria-labelledby="templates-heading" class="product-cta">
<h2 id="templates-heading">用 Claude Code 模板缩短代码审查</h2>
<p>
将实现、审查、调试和文档写作中常用的提示词,
整理成可以直接复制的形式。
</p>
<a class="primary-link" href="/zh/products">
查看教材和模板
</a>
</section>
让 Claude Code 修改这类区域时,要明确保留链接目标、统计属性和购买文案。无障碍改造不能顺手删掉变现路径。若要把检查自动化,可以把规则写进 Claude Code hooks 指南 的钩子命令中。
用例二:咨询表单和错误提示
表单是无障碍与转化率最容易同时出问题的地方。用户看不懂字段、听不到错误、无法知道哪里需要修改,就很难提交咨询或购买前问题。
下面的 React 示例可以直接复制。它把标签、帮助文本、错误、aria-invalid 和 aria-describedby 连接到对应输入框。
import { FormEvent, useState } from "react";
type Errors = {
name?: string;
email?: string;
};
export function ConsultationForm() {
const [errors, setErrors] = useState<Errors>({});
function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const data = new FormData(event.currentTarget);
const nextErrors: Errors = {};
if (!String(data.get("name") || "").trim()) {
nextErrors.name = "请输入姓名。";
}
if (!String(data.get("email") || "").includes("@")) {
nextErrors.email = "请输入有效的邮箱地址。";
}
setErrors(nextErrors);
}
return (
<form aria-labelledby="consultation-title" onSubmit={handleSubmit} noValidate>
<h2 id="consultation-title">导入咨询表单</h2>
<div className="field">
<label htmlFor="name">姓名</label>
<input
id="name"
name="name"
autoComplete="name"
aria-invalid={errors.name ? "true" : "false"}
aria-describedby={errors.name ? "name-error" : undefined}
/>
{errors.name && (
<p id="name-error" role="alert">
{errors.name}
</p>
)}
</div>
<div className="field">
<label htmlFor="email">邮箱</label>
<p id="email-help">请填写可以接收回复的工作邮箱。</p>
<input
id="email"
name="email"
type="email"
autoComplete="email"
aria-invalid={errors.email ? "true" : "false"}
aria-describedby={
errors.email ? "email-help email-error" : "email-help"
}
/>
{errors.email && (
<p id="email-error" role="alert">
{errors.email}
</p>
)}
</div>
<button type="submit">发送咨询内容</button>
</form>
);
}
常见失败是错误文案显示出来了,但输入框没有通过 aria-describedby 指向它。另一个失败是永远引用不存在的错误元素,后续维护时很难判断当前描述到底来自哪里。
用例三:弹窗、命令面板和菜单
弹窗类 UI 最容易“看起来完成”。真正的检查点是:打开后焦点进入弹窗,Tab 不跑到背景,Escape 可以关闭,关闭后焦点回到打开按钮。WAI-ARIA 弹窗模式 对这些行为有明确说明。
import { ReactNode, useEffect, useRef } from "react";
type ModalProps = {
open: boolean;
title: string;
onClose: () => void;
children: ReactNode;
};
const focusableSelector = [
"a[href]",
"button:not([disabled])",
"input:not([disabled])",
"select:not([disabled])",
"textarea:not([disabled])",
'[tabindex]:not([tabindex="-1"])',
].join(",");
export function AccessibleModal(props: ModalProps) {
const { open, title, onClose, children } = props;
const dialogRef = useRef<HTMLDivElement>(null);
const previousFocusRef = useRef<HTMLElement | null>(null);
useEffect(() => {
if (!open) return;
previousFocusRef.current = document.activeElement as HTMLElement;
const focusable = dialogRef.current?.querySelectorAll<HTMLElement>(
focusableSelector
);
focusable?.[0]?.focus();
function onKeyDown(event: KeyboardEvent) {
if (event.key === "Escape") onClose();
if (event.key !== "Tab" || !dialogRef.current) return;
const items = [...dialogRef.current.querySelectorAll<HTMLElement>(
focusableSelector
)];
const first = items[0];
const last = items[items.length - 1];
if (event.shiftKey && document.activeElement === first) {
event.preventDefault();
last?.focus();
} else if (!event.shiftKey && document.activeElement === last) {
event.preventDefault();
first?.focus();
}
}
document.addEventListener("keydown", onKeyDown);
return () => {
document.removeEventListener("keydown", onKeyDown);
previousFocusRef.current?.focus();
};
}, [open, onClose]);
if (!open) return null;
return (
<div className="modal-backdrop">
<div
ref={dialogRef}
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
className="modal-panel"
>
<h2 id="modal-title" tabIndex={-1}>
{title}
</h2>
{children}
<button type="button" onClick={onClose}>
关闭
</button>
</div>
</div>
);
}
菜单也要谨慎。APG 的 Menu Button Pattern 适合命令菜单,但普通网站导航通常用 nav 和链接列表更自然。过度使用 role="menu" 会改变用户对键盘操作的预期。
颜色、焦点和移动端
颜色问题常常发生在视觉优化阶段:去掉焦点外框、使用浅灰文字、只用红色表示错误。下面的 CSS 保留了可见焦点和非颜色错误提示。
.primary-link {
background: #0f766e;
border-radius: 6px;
color: #ffffff;
display: inline-flex;
font-weight: 700;
min-height: 44px;
padding: 0.75rem 1rem;
}
.primary-link:focus-visible,
button:focus-visible,
input:focus-visible {
outline: 3px solid #f59e0b;
outline-offset: 3px;
}
.field [role="alert"] {
border-left: 4px solid #b91c1c;
color: #7f1d1d;
margin-top: 0.5rem;
padding-left: 0.75rem;
}
移动端也要检查。小关闭按钮、被固定 header 遮住的焦点、过窄的点击区域,都可能在桌面检查中被漏掉。
自动化检查与读屏器检查
axe 能快速发现结构问题,但不能保证文案清楚、阅读顺序合理、业务流程可理解。因此它是门禁,不是完整审计。
npm install -D @axe-core/playwright @playwright/test
npx playwright install --with-deps chromium
import AxeBuilder from "@axe-core/playwright";
import { expect, test } from "@playwright/test";
test("consultation form has no serious accessibility issues", async ({ page }) => {
await page.goto("/contact");
const results = await new AxeBuilder({ page })
.include("main")
.withTags(["wcag2a", "wcag2aa", "wcag22aa"])
.analyze();
expect(results.violations).toEqual([]);
});
人工检查优先覆盖收入和支持路径:商品 CTA、咨询表单、购买前表单、弹窗、导航和错误恢复。Windows 可用 NVDA,macOS 可用 VoiceOver。小改动不一定需要全站审计,但必须检查这次改动触碰到的流程。
发布前失败清单
div加了role="button",但没有 Enter 和 Space 操作。- 图标按钮没有可访问名称。
- 图片
alt="image",没有表达图片目的。 aria-hidden="true"意外隐藏了弹窗或实时区域。- 错误文案可见,但没有和输入框关联。
- 焦点外框被删除,且没有替代样式。
- 弹窗关闭后焦点没有回到触发按钮。
- 自动化检查只覆盖未登录页面,没有覆盖真实表单。
把这份清单直接交给 Claude Code 做二次审查,会比泛泛要求“再检查一下”更有效。
CTA 与实际验证结果
如果你想把这套流程变成团队习惯,可以先看 Claude Code 教材与产品,再根据需要购买 50 个 Claude Code 提示词模板。团队要制定权限、审查门禁和发布验证规则时,可以从 Claude Code 培训与咨询 开始。
这次更新中,我用三个常见失败做了验证:CTA 卡片用可点击 div 实现、表单错误没有读屏宣布、弹窗关闭后焦点丢失。最稳定的 Claude Code 用法是先输出发现的问题,再生成最小补丁,最后用键盘和读屏器确认。axe 能发现很多结构问题,但错误文案是否真的清楚,仍然需要人来听一遍。
免费 PDF: Claude Code 速查表
输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。
我们会妥善保护你的信息,不发送垃圾邮件。
把 Claude Code 变成真正能带来结果的工作流
先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。
关于作者
Masa
专注 Claude Code 实务流程、团队导入和内容转化的工程师。
相关文章
Claude Code权限安全阶梯:逐步放开访问而不失控
从只读到有限编辑、验证命令和部署检查的 Claude Code 权限升级流程。
Claude Code 小PR证据包:让小改动真正可审查
用差异、验证命令、公开URL、CTA路径和回滚说明,把Claude Code的小PR变得可审查。
Claude Code 提交前 Review Gate:同时检查差异、测试、公开 URL 和 CTA
提交前用 Claude Code 审查差异范围、build、公开 URL、Gumroad 链接、咨询 CTA、缺少测试和无关文件。