web-accessibility

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Web Accessibility

Web可访问性

Build interfaces that work for everyone. These are not optional enhancements — they are baseline quality.
构建人人可用的界面。这些并非可选的增强功能,而是基础质量要求。

Semantic HTML

语义化HTML

Use the right element for the job. Never simulate interactive elements with
<div>
.
tsx
// BAD: div with click handler
<div onClick={handleClick} className="button">Submit</div>

// GOOD: semantic button
<button onClick={handleClick}>Submit</button>

// BAD: div as link
<div onClick={() => router.push('/about')}>About</div>

// GOOD: anchor/Link for navigation
<Link href="/about">About</Link>
为不同场景使用正确的元素。切勿用
<div>
模拟交互式元素。
tsx
// BAD: div with click handler
<div onClick={handleClick} className="button">Submit</div>

// GOOD: semantic button
<button onClick={handleClick}>Submit</button>

// BAD: div as link
<div onClick={() => router.push('/about')}>About</div>

// GOOD: anchor/Link for navigation
<Link href="/about">About</Link>

Element Selection Guide

元素选择指南

PurposeElementNot
Action (submit, toggle, delete)
<button>
<div onClick>
Navigation to URL
<a>
/
<Link>
<button onClick={navigate}>
Form input
<input>
,
<select>
,
<textarea>
Custom div-based inputs
Section heading
<h1>
<h6>
(sequential)
<div className="heading">
List of items
<ul>
/
<ol>
+
<li>
Repeated
<div>
Navigation group
<nav>
<div className="nav">
Main content
<main>
<div id="content">
用途元素避免使用
操作(提交、切换、删除)
<button>
<div onClick>
跳转到URL
<a>
/
<Link>
<button onClick={navigate}>
表单输入
<input>
,
<select>
,
<textarea>
基于div的自定义输入框
章节标题
<h1>
<h6>
(按顺序使用)
<div className="heading">
项目列表
<ul>
/
<ol>
+
<li>
重复的
<div>
导航组
<nav>
<div className="nav">
主要内容
<main>
<div id="content">

Keyboard Navigation

键盘导航

Every interactive element must be keyboard accessible.
所有交互式元素都必须支持键盘访问。

Focus Management

焦点管理

css
/* NEVER remove focus indicators without replacement */
/* BAD */
*:focus { outline: none; }

/* GOOD: Visible focus only on keyboard navigation */
.interactive:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* Group focus for compound controls */
.input-group:focus-within {
  outline: 2px solid var(--color-accent);
}
css
/* NEVER remove focus indicators without replacement */
/* BAD */
*:focus { outline: none; }

/* GOOD: Visible focus only on keyboard navigation */
.interactive:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* Group focus for compound controls */
.input-group:focus-within {
  outline: 2px solid var(--color-accent);
}

Keyboard Event Handling

键盘事件处理

tsx
// Interactive custom elements need keyboard support
function CustomButton({ onClick, children }: { onClick: () => void; children: React.ReactNode }) {
  return (
    <div
      role="button"
      tabIndex={0}
      onClick={onClick}
      onKeyDown={(e) => {
        if (e.key === 'Enter' || e.key === ' ') {
          e.preventDefault();
          onClick();
        }
      }}
    >
      {children}
    </div>
  );
}
// Better: just use <button> and avoid all of the above
tsx
// Interactive custom elements need keyboard support
function CustomButton({ onClick, children }: { onClick: () => void; children: React.ReactNode }) {
  return (
    <div
      role="button"
      tabIndex={0}
      onClick={onClick}
      onKeyDown={(e) => {
        if (e.key === 'Enter' || e.key === ' ') {
          e.preventDefault();
          onClick();
        }
      }}
    >
      {children}
    </div>
  );
}
// Better: just use <button> and avoid all of the above

Skip Links

跳转链接

Provide skip navigation for keyboard users.
tsx
<a href="#main-content" className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50">
  Skip to main content
</a>
Headings used as scroll targets need offset for fixed headers:
css
[id] { scroll-margin-top: 5rem; }
为键盘用户提供跳过导航的功能。
tsx
<a href="#main-content" className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50">
  Skip to main content
</a>
用作滚动目标的标题需要为固定头部设置偏移:
css
[id] { scroll-margin-top: 5rem; }

ARIA Patterns

ARIA模式

Icon Buttons

图标按钮

tsx
// Icon-only buttons MUST have aria-label
<button aria-label="Close dialog" onClick={onClose}>
  <XIcon aria-hidden="true" />
</button>

// Decorative icons are hidden from screen readers
<span aria-hidden="true">🔒</span> Secure connection
tsx
// Icon-only buttons MUST have aria-label
<button aria-label="Close dialog" onClick={onClose}>
  <XIcon aria-hidden="true" />
</button>

// Decorative icons are hidden from screen readers
<span aria-hidden="true">🔒</span> Secure connection

Live Regions

实时区域

Announce dynamic content changes to screen readers.
tsx
// Toast notifications
<div role="status" aria-live="polite">
  {notification && <p>{notification.message}</p>}
</div>

// Error alerts
<div role="alert" aria-live="assertive">
  {error && <p>{error.message}</p>}
</div>
向屏幕阅读器播报动态内容的变化。
tsx
// Toast notifications
<div role="status" aria-live="polite">
  {notification && <p>{notification.message}</p>}
</div>

// Error alerts
<div role="alert" aria-live="assertive">
  {error && <p>{error.message}</p>}
</div>

Loading States

加载状态

tsx
<button disabled={isLoading} aria-busy={isLoading}>
  {isLoading ? 'Saving\u2026' : 'Save'}  {/* proper ellipsis character */}
</button>

// Skeleton screens
<div aria-busy="true" aria-label="Loading content">
  <Skeleton />
</div>
tsx
<button disabled={isLoading} aria-busy={isLoading}>
  {isLoading ? 'Saving\u2026' : 'Save'}  {/* proper ellipsis character */}
</button>

// Skeleton screens
<div aria-busy="true" aria-label="Loading content">
  <Skeleton />
</div>

Forms

表单

Labels

标签

Every input must have an associated label.
tsx
// GOOD: Explicit association
<label htmlFor="email">Email</label>
<input id="email" type="email" autoComplete="email" />

// GOOD: Wrapping (clickable label, no htmlFor needed)
<label>
  Email
  <input type="email" autoComplete="email" />
</label>

// GOOD: Visually hidden but accessible
<label htmlFor="search" className="sr-only">Search</label>
<input id="search" type="search" placeholder="Search..." />
每个输入框都必须关联对应的标签。
tsx
// GOOD: Explicit association
<label htmlFor="email">Email</label>
<input id="email" type="email" autoComplete="email" />

// GOOD: Wrapping (clickable label, no htmlFor needed)
<label>
  Email
  <input type="email" autoComplete="email" />
</label>

// GOOD: Visually hidden but accessible
<label htmlFor="search" className="sr-only">Search</label>
<input id="search" type="search" placeholder="Search..." />

Input Types and Autocomplete

输入类型与自动补全

Use semantic input types to get the right mobile keyboard and browser behavior.
tsx
<input type="email" autoComplete="email" />
<input type="tel" autoComplete="tel" />
<input type="url" autoComplete="url" />
<input type="password" autoComplete="current-password" />
<input type="password" autoComplete="new-password" />
使用语义化输入类型以获得适配的移动端键盘和浏览器行为。
tsx
<input type="email" autoComplete="email" />
<input type="tel" autoComplete="tel" />
<input type="url" autoComplete="url" />
<input type="password" autoComplete="current-password" />
<input type="password" autoComplete="new-password" />

Validation and Errors

验证与错误提示

tsx
<div>
  <label htmlFor="email">Email</label>
  <input
    id="email"
    type="email"
    aria-invalid={!!errors.email}
    aria-describedby={errors.email ? 'email-error' : undefined}
  />
  {errors.email && (
    <p id="email-error" role="alert" className="text-red-600">
      {errors.email}
    </p>
  )}
</div>
tsx
<div>
  <label htmlFor="email">Email</label>
  <input
    id="email"
    type="email"
    aria-invalid={!!errors.email}
    aria-describedby={errors.email ? 'email-error' : undefined}
  />
  {errors.email && (
    <p id="email-error" role="alert" className="text-red-600">
      {errors.email}
    </p>
  )}
</div>

Form Behavior Rules

表单行为规则

  • Never prevent paste on any input
  • Disable spellcheck on emails and codes:
    spellCheck={false}
  • Submit button stays enabled until request starts; show spinner during loading
  • Focus first error on submit failure
  • Checkboxes and radio buttons: single hit target, no dead zones between label and input
  • 切勿禁止粘贴任何输入框的内容
  • 关闭拼写检查:邮箱和验证码输入框设置
    spellCheck={false}
  • 提交按钮保持启用状态直到请求开始;加载期间显示加载动画
  • 提交失败时聚焦第一个错误输入框
  • 复选框和单选按钮:单个点击目标,标签与输入框之间无无效区域

Images and Media

图片与媒体

tsx
// Informative images: descriptive alt
<img src="chart.png" alt="Revenue grew 40% from Q1 to Q3 2025" />

// Decorative images: empty alt
<img src="divider.svg" alt="" />

// Prevent layout shift: always set dimensions
<img src="photo.jpg" width={800} height={600} alt="Team photo" />

// Below fold: lazy load
<img src="photo.jpg" loading="lazy" alt="..." />

// Critical: prioritize
<img src="hero.jpg" fetchPriority="high" alt="..." />
tsx
// Informative images: descriptive alt
<img src="chart.png" alt="Revenue grew 40% from Q1 to Q3 2025" />

// Decorative images: empty alt
<img src="divider.svg" alt="" />

// Prevent layout shift: always set dimensions
<img src="photo.jpg" width={800} height={600} alt="Team photo" />

// Below fold: lazy load
<img src="photo.jpg" loading="lazy" alt="..." />

// Critical: prioritize
<img src="hero.jpg" fetchPriority="high" alt="..." />

Touch and Mobile

触摸与移动端适配

Touch Targets

触摸目标

Minimum 44x44px for all interactive elements (WCAG 2.5.5).
css
.touch-target {
  min-height: 44px;
  min-width: 44px;
}
所有交互式元素的触摸目标最小尺寸为44x44px(符合WCAG 2.5.5标准)。
css
.touch-target {
  min-height: 44px;
  min-width: 44px;
}

Safe Areas

安全区域适配

Handle device notches and home indicators.
css
.full-bleed {
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}
适配设备刘海和底部指示条。
css
.full-bleed {
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}

Touch Behavior

触摸行为优化

css
/* Prevent 300ms delay and highlight flash */
.interactive {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

/* Prevent scroll chaining on modals */
.modal { overscroll-behavior: contain; }
css
/* Prevent 300ms delay and highlight flash */
.interactive {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

/* Prevent scroll chaining on modals */
.modal { overscroll-behavior: contain; }

Internationalization

国际化

tsx
// BAD: Hardcoded formats
const date = `${month}/${day}/${year}`;
const price = `$${amount.toFixed(2)}`;

// GOOD: Locale-aware formatting
const date = new Intl.DateTimeFormat(locale).format(new Date());
const price = new Intl.NumberFormat(locale, {
  style: 'currency',
  currency: 'USD',
}).format(amount);

// Detect language
const lang = request.headers.get('accept-language')?.split(',')[0] ?? 'en';
tsx
// BAD: Hardcoded formats
const date = `${month}/${day}/${year}`;
const price = `$${amount.toFixed(2)}`;

// GOOD: Locale-aware formatting
const date = new Intl.DateTimeFormat(locale).format(new Date());
const price = new Intl.NumberFormat(locale, {
  style: 'currency',
  currency: 'USD',
}).format(amount);

// Detect language
const lang = request.headers.get('accept-language')?.split(',')[0] ?? 'en';

Performance for Accessibility

可访问性相关性能优化

  • Lists > 50 items: virtualize
  • Critical fonts:
    <link rel="preload" as="font">
    with
    font-display: swap
  • Avoid layout reads during render (causes jank for screen reader users too)
  • Uncontrolled inputs perform better than controlled for large forms
  • 超过50条数据的列表:使用虚拟滚动
  • 关键字体:使用
    <link rel="preload" as="font">
    并设置
    font-display: swap
  • 避免在渲染期间读取布局(会导致界面卡顿,影响屏幕阅读器用户)
  • 大型表单使用非受控输入框比受控输入框性能更好

Checklist

检查清单

Use this for review:
  • All interactive elements are keyboard accessible
  • Focus indicators are visible on
    :focus-visible
  • Color contrast meets WCAG AA (4.5:1 body, 3:1 large text)
  • Images have appropriate alt text
  • Form inputs have labels
  • Error messages are associated with inputs via
    aria-describedby
  • Icon-only buttons have
    aria-label
  • Dynamic content uses
    aria-live
    regions
  • Touch targets are minimum 44x44px
  • prefers-reduced-motion
    is respected
  • No
    outline: none
    without replacement focus style
  • Heading hierarchy is sequential (no skipped levels)
用于审查时使用:
  • 所有交互式元素支持键盘访问
  • 焦点指示器在
    :focus-visible
    状态下可见
  • 颜色对比度符合WCAG AA标准(正文4.5:1,大文本3:1)
  • 图片配有合适的替代文本
  • 表单输入框都有关联标签
  • 错误提示通过
    aria-describedby
    与输入框关联
  • 仅含图标的按钮设置了
    aria-label
  • 动态内容使用
    aria-live
    区域
  • 触摸目标最小尺寸为44x44px
  • 尊重
    prefers-reduced-motion
    设置
  • 没有在未替换焦点样式的情况下使用
    outline: none
  • 标题层级连续(无层级跳跃)