standards-accessibility

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Accessibility Standards

可访问性标准

Core Rule: Build accessible interfaces that work for all users, including those using assistive technologies.
核心准则: 构建能为所有用户(包括使用辅助技术的用户)所用的可访问界面。

When to use this skill

何时使用此技能

  • When creating or modifying frontend components (React, Vue, Svelte, web components, etc.)
  • When writing HTML templates or JSX/TSX component markup
  • When implementing forms and ensuring all inputs have proper labels
  • When adding images and needing to provide descriptive alt text
  • When building interactive elements that need keyboard navigation support
  • When implementing focus management in modals, dialogs, or single-page applications
  • When ensuring color contrast ratios meet WCAG standards (4.5:1 for normal text)
  • When adding ARIA attributes to enhance complex component accessibility
  • When creating proper heading hierarchies (h1-h6) for document structure
  • When testing components with screen readers or accessibility testing tools
  • When building navigation menus, buttons, or links that need to be keyboard accessible
This Skill provides Claude Code with specific guidance on how to adhere to coding standards as they relate to how it should handle frontend accessibility.
  • 创建或修改前端组件(React、Vue、Svelte、Web Components等)时
  • 编写HTML模板或JSX/TSX组件标记时
  • 实现表单并确保所有输入项都有合适的标签时
  • 添加图片并需要提供描述性替代文本时
  • 构建需要支持键盘导航的交互元素时
  • 在模态框、对话框或单页应用中实现焦点管理时
  • 确保色彩对比度符合WCAG标准(普通文本为4.5:1)时
  • 添加ARIA属性以增强复杂组件的可访问性时
  • 为文档结构创建合理的标题层级(h1-h6)时
  • 使用屏幕阅读器或可访问性测试工具测试组件时
  • 构建需要支持键盘访问的导航菜单、按钮或链接时
此技能为Claude Code提供了与前端可访问性相关的编码标准指导,说明其应如何处理前端可访问性问题。

Semantic HTML First

优先使用语义化HTML

Use native HTML elements that convey meaning to assistive technologies.
Correct elements:
html
<!-- Navigation -->
<nav><a href="/about">About</a></nav>

<!-- Buttons that perform actions -->
<button onClick="{handleSubmit}">Submit</button>

<!-- Links that navigate -->
<a href="/profile">View Profile</a>

<!-- Main content area -->
<main><article>...</article></main>

<!-- Form structure -->
<form>
  <label for="email">Email</label>
  <input id="email" type="email" />
</form>
Avoid:
html
<!-- BAD - div/span without semantic meaning -->
<div onClick="{navigate}">Go to page</div>
<span onClick="{handleClick}">Submit</span>
When to use each element:
  • <button>
    : Actions (submit, open modal, toggle)
  • <a>
    : Navigation to different pages/sections
  • <nav>
    : Navigation landmarks
  • <main>
    : Primary page content
  • <header>
    ,
    <footer>
    ,
    <aside>
    : Page structure
  • <article>
    ,
    <section>
    : Content grouping
使用能向辅助技术传达含义的原生HTML元素。
正确的元素:
html
<!-- Navigation -->
<nav><a href="/about">About</a></nav>

<!-- Buttons that perform actions -->
<button onClick="{handleSubmit}">Submit</button>

<!-- Links that navigate -->
<a href="/profile">View Profile</a>

<!-- Main content area -->
<main><article>...</article></main>

<!-- Form structure -->
<form>
  <label for="email">Email</label>
  <input id="email" type="email" />
</form>
应避免:
html
<!-- BAD - div/span without semantic meaning -->
<div onClick="{navigate}">Go to page</div>
<span onClick="{handleClick}">Submit</span>
各元素的使用场景:
  • <button>
    :用于操作(提交、打开模态框、切换状态等)
  • <a>
    :用于跳转到不同页面/区域
  • <nav>
    :导航地标
  • <main>
    :页面主要内容区域
  • <header>
    <footer>
    <aside>
    :页面结构元素
  • <article>
    <section>
    :内容分组元素

Keyboard Navigation

键盘导航

All interactive elements must be keyboard accessible.
Requirements:
  • Tab key moves focus through interactive elements
  • Enter/Space activates buttons and links
  • Escape closes modals and dialogs
  • Arrow keys navigate menus and lists (when appropriate)
  • Focus indicators are clearly visible
Implementation:
jsx
// Native elements are keyboard accessible by default
<button onClick={handleClick}>Click me</button>

// Custom interactive elements need tabIndex
<div
  role="button"
  tabIndex={0}
  onClick={handleClick}
  onKeyDown={(e) => {
    if (e.key === 'Enter' || e.key === ' ') {
      handleClick();
    }
  }}
>
  Custom button
</div>

// Focus styles must be visible
button:focus {
  outline: 2px solid blue;
  outline-offset: 2px;
}
Never:
  • Remove focus outlines without providing alternative indicators
  • Use
    tabIndex
    values other than 0 or -1
  • Create keyboard traps (user can't escape with keyboard)
所有交互元素必须支持键盘访问。
要求:
  • Tab键可在交互元素间移动焦点
  • Enter/Space键可激活按钮和链接
  • Escape键可关闭模态框和对话框
  • 箭头键可导航菜单和列表(适用场景)
  • 焦点指示器清晰可见
实现方式:
jsx
// Native elements are keyboard accessible by default
<button onClick={handleClick}>Click me</button>

// Custom interactive elements need tabIndex
<div
  role="button"
  tabIndex={0}
  onClick={handleClick}
  onKeyDown={(e) => {
    if (e.key === 'Enter' || e.key === ' ') {
      handleClick();
    }
  }}
>
  Custom button
</div>

// Focus styles must be visible
button:focus {
  outline: 2px solid blue;
  outline-offset: 2px;
}
绝对不要:
  • 在未提供替代指示器的情况下移除焦点轮廓
  • 使用0或-1以外的
    tabIndex
  • 创建键盘陷阱(用户无法通过键盘退出)

Form Labels and Inputs

表单标签与输入项

Every form input must have an associated label.
Correct patterns:
html
<!-- Explicit label association -->
<label for="username">Username</label>
<input id="username" type="text" />

<!-- Implicit label wrapping -->
<label>
  Email
  <input type="email" />
</label>

<!-- aria-label for icon-only buttons -->
<button aria-label="Close dialog">
  <CloseIcon />
</button>

<!-- aria-describedby for help text -->
<label for="password">Password</label>
<input id="password" type="password" aria-describedby="password-help" />
<span id="password-help">Must be at least 8 characters</span>
Required attributes:
  • id
    on input, matching
    for
    on label
  • type
    attribute on inputs (text, email, password, etc.)
  • aria-label
    or
    aria-labelledby
    when visual label isn't present
  • aria-describedby
    for additional context or error messages
每个表单输入项必须关联对应的标签。
正确模式:
html
<!-- Explicit label association -->
<label for="username">Username</label>
<input id="username" type="text" />

<!-- Implicit label wrapping -->
<label>
  Email
  <input type="email" />
</label>

<!-- aria-label for icon-only buttons -->
<button aria-label="Close dialog">
  <CloseIcon />
</button>

<!-- aria-describedby for help text -->
<label for="password">Password</label>
<input id="password" type="password" aria-describedby="password-help" />
<span id="password-help">Must be at least 8 characters</span>
必填属性:
  • 输入项上的
    id
    需与标签的
    for
    属性匹配
  • 输入项的
    type
    属性(text、email、password等)
  • 当无视觉标签时使用
    aria-label
    aria-labelledby
  • 使用
    aria-describedby
    提供额外上下文或错误提示

Alternative Text for Images

图片替代文本

Provide descriptive alt text that conveys the image's purpose.
Guidelines:
jsx
<!-- Informative images -->
<img src="chart.png" alt="Sales increased 40% in Q4 2024" />

<!-- Functional images (buttons, links) -->
<a href="/search">
  <img src="search-icon.svg" alt="Search" />
</a>

<!-- Decorative images -->
<img src="decoration.png" alt="" />
{/* or */}
<img src="decoration.png" role="presentation" />

<!-- Complex images need longer descriptions -->
<img
  src="architecture.png"
  alt="System architecture diagram"
  aria-describedby="arch-description"
/>
<div id="arch-description">
  The system consists of three layers: frontend React app,
  Node.js API server, and PostgreSQL database...
</div>
Alt text rules:
  • Describe the content and function, not "image of"
  • Keep concise (under 150 characters when possible)
  • Use empty alt (
    alt=""
    ) for purely decorative images
  • Don't include "image", "picture", "photo" (screen readers announce this)
提供能传达图片用途的描述性替代文本。
指导原则:
jsx
<!-- Informative images -->
<img src="chart.png" alt="Sales increased 40% in Q4 2024" />

<!-- Functional images (buttons, links) -->
<a href="/search">
  <img src="search-icon.svg" alt="Search" />
</a>

<!-- Decorative images -->
<img src="decoration.png" alt="" />
{/* or */}
<img src="decoration.png" role="presentation" />

<!-- Complex images need longer descriptions -->
<img
  src="architecture.png"
  alt="System architecture diagram"
  aria-describedby="arch-description"
/>
<div id="arch-description">
  The system consists of three layers: frontend React app,
  Node.js API server, and PostgreSQL database...
</div>
替代文本规则:
  • 描述内容和功能,而非“图片展示了...”
  • 保持简洁(尽可能控制在150字符以内)
  • 纯装饰性图片使用空替代文本(
    alt=""
  • 不要包含“图片”“照片”等词(屏幕阅读器会自动播报)

Color Contrast

色彩对比度

Maintain sufficient contrast ratios for readability.
WCAG Requirements:
  • Normal text (< 18pt): 4.5:1 contrast ratio
  • Large text (≥ 18pt or ≥ 14pt bold): 3:1 contrast ratio
  • UI components and graphics: 3:1 contrast ratio
Don't rely on color alone:
jsx
// BAD - color only
<span style={{color: 'red'}}>Error</span>

// GOOD - color + icon + text
<span style={{color: 'red'}}>
  <ErrorIcon aria-hidden="true" />
  Error: Invalid email format
</span>

// BAD - color-coded status
<div style={{backgroundColor: status === 'active' ? 'green' : 'red'}} />

// GOOD - color + text label
<div>
  <StatusBadge color={status === 'active' ? 'green' : 'red'}>
    {status === 'active' ? 'Active' : 'Inactive'}
  </StatusBadge>
</div>
Tools to verify contrast:
  • Browser DevTools (Chrome, Firefox have built-in checkers)
  • WebAIM Contrast Checker
  • Axe DevTools extension
保持足够的对比度以确保可读性。
WCAG要求:
  • 普通文本(<18pt):4.5:1的对比度
  • 大文本(≥18pt或≥14pt粗体):3:1的对比度
  • UI组件和图形:3:1的对比度
不要仅依赖色彩传达信息:
jsx
// BAD - color only
<span style={{color: 'red'}}>Error</span>

// GOOD - color + icon + text
<span style={{color: 'red'}}>
  <ErrorIcon aria-hidden="true" />
  Error: Invalid email format
</span>

// BAD - color-coded status
<div style={{backgroundColor: status === 'active' ? 'green' : 'red'}} />

// GOOD - color + text label
<div>
  <StatusBadge color={status === 'active' ? 'green' : 'red'}>
    {status === 'active' ? 'Active' : 'Inactive'}
  </StatusBadge>
</div>
对比度验证工具:
  • 浏览器开发者工具(Chrome、Firefox内置检查器)
  • WebAIM对比度检查器
  • Axe DevTools扩展

ARIA Attributes

ARIA属性

Use ARIA to enhance semantics when HTML alone isn't sufficient.
Common ARIA patterns:
jsx
// Roles for custom components
<div role="dialog" aria-modal="true">
  <h2 id="dialog-title">Confirm Action</h2>
  <div aria-describedby="dialog-desc">...</div>
</div>

// States and properties
<button aria-expanded={isOpen} aria-controls="menu">
  Menu
</button>
<ul id="menu" hidden={!isOpen}>...</ul>

// Live regions for dynamic content
<div aria-live="polite" aria-atomic="true">
  {statusMessage}
</div>

// Hide decorative elements
<span aria-hidden="true"></span>
ARIA rules:
  1. Use semantic HTML first, ARIA second
  2. Don't override native semantics (
    <button role="link">
    is wrong)
  3. All interactive ARIA roles need keyboard support
  4. Test with actual screen readers
Common ARIA attributes:
  • aria-label
    : Accessible name for element
  • aria-labelledby
    : References element(s) that label this one
  • aria-describedby
    : References element(s) that describe this one
  • aria-expanded
    : Whether element is expanded (true/false)
  • aria-hidden
    : Hide from assistive tech (use sparingly)
  • aria-live
    : Announce dynamic content changes (polite/assertive)
当HTML本身无法满足需求时,使用ARIA增强语义。
常见ARIA模式:
jsx
// Roles for custom components
<div role="dialog" aria-modal="true">
  <h2 id="dialog-title">Confirm Action</h2>
  <div aria-describedby="dialog-desc">...</div>
</div>

// States and properties
<button aria-expanded={isOpen} aria-controls="menu">
  Menu
</button>
<ul id="menu" hidden={!isOpen}>...</ul>

// Live regions for dynamic content
<div aria-live="polite" aria-atomic="true">
  {statusMessage}
</div>

// Hide decorative elements
<span aria-hidden="true"></span>
ARIA规则:
  1. 优先使用语义化HTML,其次才是ARIA
  2. 不要覆盖原生语义(
    <button role="link">
    是错误的)
  3. 所有带ARIA交互角色的元素都需要支持键盘操作
  4. 使用实际的屏幕阅读器进行测试
常见ARIA属性:
  • aria-label
    :元素的可访问名称
  • aria-labelledby
    :引用为当前元素提供标签的元素
  • aria-describedby
    :引用为当前元素提供描述的元素
  • aria-expanded
    :元素是否展开(true/false)
  • aria-hidden
    :对辅助技术隐藏元素(谨慎使用)
  • aria-live
    :播报动态内容变化(polite/assertive)

Heading Hierarchy

标题层级

Use heading levels (h1-h6) in logical order to create document structure.
Correct structure:
html
<h1>Page Title</h1>
<h2>Section 1</h2>
<h3>Subsection 1.1</h3>
<h3>Subsection 1.2</h3>
<h2>Section 2</h2>
<h3>Subsection 2.1</h3>
Rules:
  • One
    <h1>
    per page (page title)
  • Don't skip levels (h2 → h4 is wrong)
  • Don't choose headings based on visual size (use CSS for styling)
  • Headings create an outline for screen reader navigation
Styling headings:
css
/* Separate semantic level from visual appearance */
h1 {
  font-size: 2rem;
}
h2 {
  font-size: 1.5rem;
}

/* If you need h3 to look like h1 */
.h3-large {
  font-size: 2rem;
}
按逻辑顺序使用标题层级(h1-h6)构建文档结构。
正确结构:
html
<h1>Page Title</h1>
<h2>Section 1</h2>
<h3>Subsection 1.1</h3>
<h3>Subsection 1.2</h3>
<h2>Section 2</h2>
<h3>Subsection 2.1</h3>
规则:
  • 每页仅一个
    <h1>
    (页面标题)
  • 不要跳过层级(h2→h4是错误的)
  • 不要根据视觉大小选择标题(使用CSS进行样式调整)
  • 标题为屏幕阅读器导航提供大纲
标题样式:
css
/* Separate semantic level from visual appearance */
h1 {
  font-size: 2rem;
}
h2 {
  font-size: 1.5rem;
}

/* If you need h3 to look like h1 */
.h3-large {
  font-size: 2rem;
}

Focus Management

焦点管理

Manage focus in dynamic interfaces to maintain keyboard navigation flow.
Modal dialogs:
jsx
function Modal({ isOpen, onClose, children }) {
  const modalRef = useRef();

  useEffect(() => {
    if (isOpen) {
      // Save previously focused element
      const previousFocus = document.activeElement;

      // Move focus to modal
      modalRef.current?.focus();

      // Trap focus within modal
      // (use library like focus-trap-react)

      return () => {
        // Restore focus when modal closes
        previousFocus?.focus();
      };
    }
  }, [isOpen]);

  return (
    <div ref={modalRef} role="dialog" aria-modal="true" tabIndex={-1}>
      {children}
      <button onClick={onClose}>Close</button>
    </div>
  );
}
Dynamic content:
jsx
// Announce content changes to screen readers
<div aria-live="polite">{loading ? "Loading..." : `Loaded ${items.length} items`}</div>;

// Move focus to new content after navigation
function handlePageChange(newPage) {
  loadPage(newPage);
  // Focus the main heading of new content
  document.querySelector("h1")?.focus();
}
在动态界面中管理焦点,以保持键盘导航的流畅性。
模态对话框:
jsx
function Modal({ isOpen, onClose, children }) {
  const modalRef = useRef();

  useEffect(() => {
    if (isOpen) {
      // Save previously focused element
      const previousFocus = document.activeElement;

      // Move focus to modal
      modalRef.current?.focus();

      // Trap focus within modal
      // (use library like focus-trap-react)

      return () => {
        // Restore focus when modal closes
        previousFocus?.focus();
      };
    }
  }, [isOpen]);

  return (
    <div ref={modalRef} role="dialog" aria-modal="true" tabIndex={-1}>
      {children}
      <button onClick={onClose}>Close</button>
    </div>
  );
}
动态内容:
jsx
// Announce content changes to screen readers
<div aria-live="polite">{loading ? "Loading..." : `Loaded ${items.length} items`}</div>;

// Move focus to new content after navigation
function handlePageChange(newPage) {
  loadPage(newPage);
  // Focus the main heading of new content
  document.querySelector("h1")?.focus();
}

Verification Checklist

验证检查清单

Before marking UI work complete:
  • All interactive elements are keyboard accessible
  • Focus indicators are visible on all focusable elements
  • All images have appropriate alt text
  • All form inputs have associated labels
  • Color contrast meets WCAG standards (4.5:1 for text)
  • Heading hierarchy is logical (no skipped levels)
  • ARIA attributes are used correctly (if needed)
  • Modals and dialogs manage focus appropriately
  • No information conveyed by color alone
  • Tested with keyboard navigation (Tab, Enter, Escape)
在完成UI工作前,请检查:
  • 所有交互元素都支持键盘访问
  • 所有可聚焦元素的焦点指示器可见
  • 所有图片都有合适的替代文本
  • 所有表单输入项都有关联标签
  • 色彩对比度符合WCAG标准(文本为4.5:1)
  • 标题层级逻辑合理(无跳过层级)
  • ARIA属性使用正确(如需使用)
  • 模态框和对话框正确管理焦点
  • 没有仅依赖色彩传达的信息
  • 已通过键盘导航测试(Tab、Enter、Escape)

Common Mistakes to Avoid

需避免的常见错误

Using divs/spans for buttons:
jsx
// BAD
<div onClick={handleClick}>Submit</div>

// GOOD
<button onClick={handleClick}>Submit</button>
Missing form labels:
jsx
// BAD
<input type="text" placeholder="Username" />

// GOOD
<label for="username">Username</label>
<input id="username" type="text" />
Removing focus outlines:
css
/* BAD */
button:focus {
  outline: none;
}

/* GOOD - provide alternative indicator */
button:focus {
  outline: 2px solid blue;
  outline-offset: 2px;
}
Redundant ARIA:
jsx
// BAD - button already has button role
<button role="button">Click</button>

// GOOD - use native semantics
<button>Click</button>
Inaccessible custom components:
jsx
// BAD - no keyboard support
<div onClick={handleClick}>Custom button</div>

// GOOD - full keyboard support
<div
  role="button"
  tabIndex={0}
  onClick={handleClick}
  onKeyDown={(e) => {
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      handleClick();
    }
  }}
>
  Custom button
</div>
使用div/span作为按钮:
jsx
// BAD
<div onClick={handleClick}>Submit</div>

// GOOD
<button onClick={handleClick}>Submit</button>
缺少表单标签:
jsx
// BAD
<input type="text" placeholder="Username" />

// GOOD
<label for="username">Username</label>
<input id="username" type="text" />
移除焦点轮廓:
css
/* BAD */
button:focus {
  outline: none;
}

/* GOOD - provide alternative indicator */
button:focus {
  outline: 2px solid blue;
  outline-offset: 2px;
}
冗余的ARIA:
jsx
// BAD - button already has button role
<button role="button">Click</button>

// GOOD - use native semantics
<button>Click</button>
不可访问的自定义组件:
jsx
// BAD - no keyboard support
<div onClick={handleClick}>Custom button</div>

// GOOD - full keyboard support
<div
  role="button"
  tabIndex={0}
  onClick={handleClick}
  onKeyDown={(e) => {
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      handleClick();
    }
  }}
>
  Custom button
</div>

Testing Accessibility

可访问性测试

Manual testing:
  1. Navigate entire interface using only keyboard
  2. Verify all interactive elements are reachable and activatable
  3. Check focus indicators are visible
  4. Test with browser zoom at 200%
  5. Use browser DevTools accessibility inspector
Automated testing:
  • Axe DevTools browser extension
  • Lighthouse accessibility audit
  • WAVE browser extension
  • eslint-plugin-jsx-a11y (for React)
Screen reader testing:
  • macOS: VoiceOver (Cmd+F5)
  • Windows: NVDA (free) or JAWS
  • Test critical user flows with screen reader enabled
Remember: Automated tools catch ~30% of issues. Manual testing is essential.
手动测试:
  1. 仅使用键盘导航整个界面
  2. 验证所有交互元素均可被访问和激活
  3. 检查焦点指示器可见
  4. 在200%浏览器缩放比例下测试
  5. 使用浏览器开发者工具的可访问性检查器
自动化测试:
  • Axe DevTools浏览器扩展
  • Lighthouse可访问性审计
  • WAVE浏览器扩展
  • eslint-plugin-jsx-a11y(适用于React)
屏幕阅读器测试:
  • macOS:VoiceOver(Cmd+F5)
  • Windows:NVDA(免费)或JAWS
  • 启用屏幕阅读器测试关键用户流程
注意: 自动化工具仅能检测约30%的问题,手动测试至关重要。