mcpField
W
wcag

wcag

github.com/claude-dev-suite设计与 UI4640 次安装2 天前更新
代码仓库

关于 wcag

介绍 WCAG 2.2 无障碍原则、成功准则和实施方法的参考技能,适用于评估 Web 内容与界面的设计和开发人员,涵盖设计、开发及手动和自动化测试。

主要功能

  1. 01WCAG 2.2 原则与准则
  2. 02感知、可操作、可理解、健壮指导
  3. 03键盘操作与焦点实践
  4. 04无障碍表单与错误处理
  5. 05自动化和手动测试清单

适用场景

  1. 01按无障碍准则审查界面设计
  2. 02改进键盘导航和焦点管理
  3. 03检查表单标签和错误提示是否无障碍
  4. 04规划手动和自动化无障碍审查

MCP 相关

该技能指向 `mcp__documentation__fetch_docs`,用于获取完整的 WCAG 指导。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill wcag

安装后在 Claude Code 中输入/wcag确认已加载。

详细介绍

WCAG 2.2 无障碍访问

深入了解:使用 mcp__documentation__fetch_docs,并将 technology 设为 wcag,获取全面的 WCAG 指南、成功标准和技术。

不要使用此技能的情况

  • 自动化测试配置 - 使用 axe-core 技能集成 axe 测试工具
  • 组件库无障碍访问 - 使用特定框架的技能(例如 React、Vue)了解无障碍组件模式
  • 设计系统 - 使用 UI 库技能获取预构建的无障碍组件
  • 仅限 ARIA 实现 - 此技能涵盖更广泛的 WCAG 合规性,而不只是 ARIA

官方参考资料

资源URL
WCAG 2.2 规范https://www.w3.org/TR/WCAG22/
快速参考https://www.w3.org/WAI/WCAG22/quickref/
WCAG 理解指南https://www.w3.org/WAI/WCAG22/Understanding/
axe-corehttps://github.com/dequelabs/axe-core
WAI-ARIAhttps://www.w3.org/TR/wai-aria-1.2/

合规等级

等级说明法律要求
A最低无障碍要求通常不足以满足要求
AA标准无障碍要求大多数法规(ADA、EN 301 549)
AAA增强型无障碍要求特定场景
WCAG 2.2 成功标准数量
等级2.2 新增总数
A030
AA624
AAA331

POUR 原则

1. 可感知
指南主要标准等级
1.1 文本替代所有非文本内容都有文本替代A
1.2 基于时间的媒体字幕、音频描述A-AAA
1.3 适应性内容结构、有意义的顺序A
1.4 可区分颜色对比度、文本缩放、间距A-AAA
// Text alternatives
<img src="chart.png" alt="Q3 sales increased 25% compared to Q2" />

// Decorative images
<img src="divider.png" alt="" role="presentation" />

// Color contrast (4.5:1 for normal text, 3:1 for large text)
// Use tools: WebAIM Contrast Checker, axe DevTools
2. 可操作
指南主要标准等级
2.1 键盘所有功能均可通过键盘使用A
2.2 充足时间可调整的时间限制A-AAA
2.4 可导航跳过链接、焦点顺序、焦点可见A-AAA
2.5 输入方式目标尺寸、动作替代方式A-AAA
// Skip link
<a href="#main-content" className="skip-link">
  Skip to main content
</a>

// Focus visible (2.4.7)
button:focus {
  outline: 3px solid #005fcc;
  outline-offset: 2px;
}

// Target size minimum (2.5.8) - 24x24 CSS pixels
.button {
  min-width: 44px;
  min-height: 44px;
  padding: 12px 16px;
}
3. 可理解
指南主要标准等级
3.1 可读页面语言、不常见词语A-AAA
3.2 可预测一致的导航、标识A-AA
3.3 输入辅助错误识别、标签A-AAA
// Language of page (3.1.1)
<html lang="en">

// Error identification (3.3.1)
<div role="alert" aria-live="assertive">
  Email is required and must be valid
</div>

// Labels (3.3.2)
<label htmlFor="email">Email Address</label>
<input id="email" type="email" aria-describedby="email-hint" />
<span id="email-hint">We'll never share your email</span>
4. 稳健
指南主要标准等级
4.1 兼容性有效的 HTML、名称/角色/值A
// Name, Role, Value (4.1.2)
<button aria-pressed="true" aria-label="Favorite this item">
  ★
</button>

// Custom controls
<div
  role="slider"
  aria-valuemin={0}
  aria-valuemax={100}
  aria-valuenow={50}
  aria-label="Volume"
  tabIndex={0}
/>

WCAG 2.2 新增内容

AA 级(必需)
标准说明
2.4.11 焦点未被遮挡(最低)焦点元素至少部分可见
2.4.13 焦点外观焦点指示器符合尺寸/对比度要求
2.5.7 拖动操作提供单指针拖动替代方式
2.5.8 目标尺寸(最低)最小尺寸为 24x24 CSS 像素
3.2.6 一致的帮助帮助信息位于一致的位置
3.3.7 冗余输入不要要求重新输入信息
AAA 级
标准说明
2.4.12 焦点未被遮挡(增强)焦点元素完全可见
3.3.8 无障碍身份验证(最低)不得要求认知功能测试
3.3.9 无障碍身份验证(增强)不得要求识别物体/内容

常见模式

模态对话框
function Modal({ isOpen, onClose, title, children }) {
  const modalRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (isOpen) {
      // Trap focus inside modal
      modalRef.current?.focus();

      // Prevent body scroll
      document.body.style.overflow = 'hidden';
    }
    return () => {
      document.body.style.overflow = '';
    };
  }, [isOpen]);

  if (!isOpen) return null;

  return (
    <div
      role="dialog"
      aria-modal="true"
      aria-labelledby="modal-title"
      ref={modalRef}
      tabIndex={-1}
      onKeyDown={(e) => e.key === 'Escape' && onClose()}
    >
      <h2 id="modal-title">{title}</h2>
      {children}
      <button onClick={onClose}>Close</button>
    </div>
  );
}
下拉菜单
function Dropdown({ label, items }) {
  const [isOpen, setIsOpen] = useState(false);
  const [activeIndex, setActiveIndex] = useState(-1);

  const handleKeyDown = (e: KeyboardEvent) => {
    switch (e.key) {
      case 'ArrowDown':
        e.preventDefault();
        setActiveIndex(i => Math.min(i + 1, items.length - 1));
        break;
      case 'ArrowUp':
        e.preventDefault();
        setActiveIndex(i => Math.max(i - 1, 0));
        break;
      case 'Escape':
        setIsOpen(false);
        break;
      case 'Enter':
      case ' ':
        if (activeIndex >= 0) items[activeIndex].onClick();
        break;
    }
  };

  return (
    <div onKeyDown={handleKeyDown}>
      <button
        aria-haspopup="menu"
        aria-expanded={isOpen}
        onClick={() => setIsOpen(!isOpen)}
      >
        {label}
      </button>
      {isOpen && (
        <ul role="menu">
          {items.map((item, i) => (
            <li
              key={item.id}
              role="menuitem"
              tabIndex={activeIndex === i ? 0 : -1}
              aria-current={activeIndex === i}
            >
              {item.label}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}
带验证的表单
function Form() {
  const [errors, setErrors] = useState<Record<string, string>>({});

  return (
    <form aria-describedby="form-errors">
      {Object.keys(errors).length > 0 && (
        <div id="form-errors" role="alert" aria-live="polite">
          <h2>Please fix the following errors:</h2>
          <ul>
            {Object.entries(errors).map(([field, msg]) => (
              <li key={field}>
                <a href={`#${field}`}>{msg}</a>
              </li>
            ))}
          </ul>
        </div>
      )}

      <div>
        <label htmlFor="email">
          Email <span aria-hidden="true">*</span>
          <span className="sr-only">(required)</span>
        </label>
        <input
          id="email"
          type="email"
          aria-required="true"
          aria-invalid={!!errors.email}
          aria-describedby={errors.email ? 'email-error' : undefined}
        />
        {errors.email && (
          <span id="email-error" role="alert">{errors.email}</span>
        )}
      </div>
    </form>
  );
}

测试

使用 axe-core 进行自动化测试
// Playwright + axe
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test('homepage has no accessibility violations', async ({ page }) => {
  await page.goto('/');

  const results = await new AxeBuilder({ page })
    .withTags(['wcag2a', 'wcag2aa', 'wcag22aa'])
    .analyze();

  expect(results.violations).toEqual([]);
});

// Vitest + axe
import { axe, toHaveNoViolations } from 'jest-axe';
import { render } from '@testing-library/react';

expect.extend(toHaveNoViolations);

test('Button is accessible', async () => {
  const { container } = render(<Button>Click me</Button>);
  const results = await axe(container);
  expect(results).toHaveNoViolations();
});
手动测试清单
## Keyboard Navigation
- [ ] Tab through all interactive elements
- [ ] Shift+Tab navigates backwards
- [ ] Enter/Space activates buttons and links
- [ ] Arrow keys work in menus, tabs, sliders
- [ ] Escape closes modals and dropdowns
- [ ] No keyboard traps

## Screen Reader
- [ ] All images have alt text
- [ ] Form fields have labels
- [ ] Headings are hierarchical (h1 > h2 > h3)
- [ ] Links are descriptive (not "click here")
- [ ] Dynamic content announced (aria-live)

## Visual
- [ ] Color contrast meets 4.5:1 (normal text)
- [ ] Color contrast meets 3:1 (large text, UI components)
- [ ] Focus indicators visible
- [ ] Content readable at 200% zoom
- [ ] No horizontal scrolling at 320px width
工具
工具用途
axe DevTools用于审计的浏览器扩展
WAVE视觉无障碍评估
Lighthouse性能和无障碍审计
NVDA/VoiceOver屏幕阅读器测试
Color Contrast Analyzer对比度检查

CSS 工具类

/* Visually hidden but accessible to screen readers */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Focus visible only for keyboard users */
:focus:not(:focus-visible) {
  outline: none;
}

:focus-visible {
  outline: 3px solid #005fcc;
  outline-offset: 2px;
}

/* Reduced motion preference */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* High contrast mode support */
@media (forced-colors: active) {
  .button {
    border: 2px solid currentColor;
  }
}

检查清单

设计阶段
  • 颜色对比度符合要求
  • 触控目标至少为 44x44(建议值)
  • 设计焦点状态
  • 错误状态包含文字,而不只是颜色
开发
  • 使用语义化 HTML
  • 仅在 HTML 不足时使用 ARIA
  • 键盘导航可正常使用
  • 为 SPA 管理焦点
  • 表单错误信息可访问
测试
  • 在 CI 流水线中使用 axe-core
  • 手动测试屏幕阅读器
  • 测试仅使用键盘进行导航
  • 测试放大至 200% 的效果

反模式

反模式错误原因正确做法
在没有 role 的 <div> 上使用 aria-label没有角色的非语义元素上的 ARIA 会被忽略优先使用语义化 HTML:使用 <button>,而不是 <div role="button">
使用 <div onclick=""> 作为按钮默认无法通过键盘访问使用 <button> 并添加适当的事件处理程序
仅使用颜色作为指示对色盲用户无效在颜色旁添加图标、文字或图案
用 placeholder 替代标签输入时会消失,且对比度低始终使用带有 for 属性的 <label>
tabindex > 0会扰乱自然的 Tab 顺序使用 tabindex="0",或依赖 DOM 顺序
在信息性图片上使用 alt=""屏幕阅读器会跳过重要内容提供描述性的替代文本
缺少焦点指示器用户无法看到当前焦点所在位置始终显示 :focus-visible 样式
自动播放媒体会让屏幕阅读器用户感到困惑需要用户操作才能开始播放媒体

快速故障排查

问题诊断解决方案
屏幕阅读器未播报按钮缺少可访问名称添加 aria-label,或在按钮内添加可见文字
模态框中键盘焦点被困住焦点管理不当根据首尾元素逻辑实现焦点陷阱
未播报表单错误缺少 role="alert" 或 aria-live在错误容器上添加 aria-live="assertive"
低对比度警告文字未达到 4.5:1 的比例使用对比度检查工具,调整颜色或增大字号
Tab 顺序不合理DOM 顺序与视觉顺序不一致重新排列 DOM,使其与视觉布局一致,并避免使用 tabindex > 0
自定义下拉菜单不可访问缺少 ARIA 角色和键盘操作支持使用 role="combobox"、aria-expanded 和方向键导航
动态内容未播报内容变化时没有提示使用 aria-live="polite" 或 role="status"
图片按钮没有标签只有图标,没有文字替代内容添加带有描述性文字的 aria-label

相关技能

评论

支持 Markdown