Tips & Tricks (更新: 2026/6/2)

用 Claude Code 落地无障碍:语义 HTML、ARIA、axe 与人工检查

用 Claude Code 做无障碍改造:语义 HTML、键盘、表单、焦点、axe 和读屏检查。

用 Claude Code 落地无障碍:语义 HTML、ARIA、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,核心路径必须可用键盘操作,并留下自动检查和人工检查记录。

领域合格线常见失败
语义 HTMLbuttonaformlabelmainnav 用在正确位置用可点击 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-invalidaria-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 能发现很多结构问题,但错误文案是否真的清楚,仍然需要人来听一遍。

#Claude Code #accessibility #WCAG #a11y #React
免费

免费 PDF: Claude Code 速查表

输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。

我们会妥善保护你的信息,不发送垃圾邮件。

把 Claude Code 变成真正能带来结果的工作流

先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。

Masa

关于作者

Masa

专注 Claude Code 实务流程、团队导入和内容转化的工程师。