关于 accessibility
Accessibility 是一项参考技能,帮助将 WCAG 2.1 和 Lighthouse 无障碍指南用于数字内容与界面。内容按 POUR 原则组织,并包含实现示例及自动化、手动测试方法。
主要功能
- 01按 POUR 原则整理 WCAG 2.1 指南
- 02提供无障碍界面模式示例
- 03涵盖色彩对比度与键盘操作
- 04提供自动化审计命令示例
- 05提供手动无障碍测试清单
适用场景
- 01检查页面的键盘访问与焦点状态
- 02核查图像替代文本和表单标签
- 03在发布前准备无障碍检查
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ tech-leads-club/ agent-skills --skill accessibility安装后在 Claude Code 中输入/accessibility确认已加载。
附带文件references/WCAG.md
详细介绍
无障碍访问(a11y)
基于 WCAG 2.1 和 Lighthouse 无障碍审计的全面无障碍指南。目标:让所有人都能使用内容,包括残障人士。
WCAG 原则:POUR
| 原则 | 说明 |
|---|---|
| Perceivable | 内容可通过不同感官感知 |
| Operable | 所有用户都能操作界面 |
| Understandable | 内容和界面易于理解 |
| Robust | 内容可与辅助技术配合使用 |
合规等级
| 等级 | 要求 | 目标 |
|---|---|---|
| A | 最低无障碍要求 | 必须通过 |
| AA | 标准合规 | 应通过(许多司法管辖区的法律要求) |
| AAA | 增强无障碍要求 | 最好达到 |
可感知性
文本替代(1.1)
图片需要替代文本:
<img src="chart.png" />
<img src="chart.png" alt="Bar chart showing 40% increase in Q3 sales" />
<img src="decorative-border.png" alt="" role="presentation" />
<figure>
<img src="infographic.png" alt="2024 market trends infographic" aria-describedby="infographic-desc" />
<figcaption id="infographic-desc">
</figcaption>
</figure>
图标按钮需要无障碍名称:
<button>
<svg></svg>
</button>
<button aria-label="Open menu">
<svg aria-hidden="true"></svg>
</button>
<button>
<svg aria-hidden="true"></svg>
<span class="visually-hidden">Open menu</span>
</button>
视觉隐藏类:
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
色彩对比度(1.4.3、1.4.6)
| 文本大小 | AA 最低要求 | AAA 增强要求 |
|---|---|---|
| 普通文本(< 18px / < 14px 粗体) | 4.5:1 | 7:1 |
| 大号文本(≥ 18px / ≥ 14px 粗体) | 3:1 | 4.5:1 |
| UI 组件和图形 | 3:1 | 3:1 |
/* ❌ Low contrast (2.5:1) */
.low-contrast {
color: #999;
background: #fff;
}
/* ✅ Sufficient contrast (7:1) */
.high-contrast {
color: #333;
background: #fff;
}
/* ✅ Focus states need contrast too */
:focus-visible {
outline: 2px solid #005fcc;
outline-offset: 2px;
}
不要仅依靠颜色传达信息:
<input class="error-border" />
<style>
.error-border {
border-color: red;
}
</style>
<div class="field-error">
<input aria-invalid="true" aria-describedby="email-error" />
<span id="email-error" class="error-message">
<svg aria-hidden="true"></svg>
Please enter a valid email address
</span>
</div>
媒体替代内容(1.2)
<video controls>
<source src="video.mp4" type="video/mp4" />
<track kind="captions" src="captions.vtt" srclang="en" label="English" default />
<track kind="descriptions" src="descriptions.vtt" srclang="en" label="Descriptions" />
</video>
<audio controls>
<source src="podcast.mp3" type="audio/mp3" />
</audio>
<details>
<summary>Transcript</summary>
<p>Full transcript text...</p>
</details>
可操作性
键盘可访问(2.1)
所有功能都必须支持键盘操作:
// ❌ Only handles click
element.addEventListener('click', handleAction)
// ✅ Handles both click and keyboard
element.addEventListener('click', handleAction)
element.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
handleAction()
}
})
不得出现键盘陷阱:
// Modal focus management
function openModal(modal) {
const focusableElements = modal.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
)
const firstElement = focusableElements[0]
const lastElement = focusableElements[focusableElements.length - 1]
// Trap focus within modal
modal.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
if (e.shiftKey && document.activeElement === firstElement) {
e.preventDefault()
lastElement.focus()
} else if (!e.shiftKey && document.activeElement === lastElement) {
e.preventDefault()
firstElement.focus()
}
}
if (e.key === 'Escape') {
closeModal()
}
})
firstElement.focus()
}
焦点可见(2.4.7)
/* ❌ Never remove focus outlines */
*:focus {
outline: none;
}
/* ✅ Use :focus-visible for keyboard-only focus */
:focus {
outline: none;
}
:focus-visible {
outline: 2px solid #005fcc;
outline-offset: 2px;
}
/* ✅ Or custom focus styles */
button:focus-visible {
box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.5);
}
跳过链接(2.4.1)
<body>
<a href="#main-content" class="skip-link">Skip to main content</a>
<header></header>
<main id="main-content" tabindex="-1">
</main>
</body>
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: #fff;
padding: 8px 16px;
z-index: 100;
}
.skip-link:focus {
top: 0;
}
时间限制(2.2)
// Allow users to extend time limits
function showSessionWarning() {
const modal = createModal({
title: 'Session Expiring',
content: 'Your session will expire in 2 minutes.',
actions: [
{ label: 'Extend session', action: extendSession },
{ label: 'Log out', action: logout },
],
timeout: 120000, // 2 minutes to respond
})
}
动效(2.3)
/* Respect 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;
scroll-behavior: auto !important;
}
}
易于理解
页面语言(3.1.1)
<html>
<html lang="en">
<p>The French word for hello is <span lang="fr">bonjour</span>.</p>
</html>
</html>
导航一致性(3.2.3)
<nav aria-label="Main">
<ul>
<li><a href="/" aria-current="page">Home</a></li>
<li><a href="/products">Products</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
表单标签(3.3.2)
<input type="email" placeholder="Email" />
<label for="email">Email address</label>
<input type="email" id="email" name="email" autocomplete="email" required />
<label>
Email address
<input type="email" name="email" autocomplete="email" required />
</label>
<label for="password">Password</label>
<input type="password" id="password" aria-describedby="password-requirements" />
<p id="password-requirements">Must be at least 8 characters with one number.</p>
错误处理(3.3.1、3.3.3)
<form novalidate>
<div class="field" aria-live="polite">
<label for="email">Email</label>
<input type="email" id="email" aria-invalid="true" aria-describedby="email-error" />
<p id="email-error" class="error" role="alert">Please enter a valid email address (e.g., [email protected])</p>
</div>
</form>
// Focus first error on submit
form.addEventListener('submit', (e) => {
const firstError = form.querySelector('[aria-invalid="true"]')
if (firstError) {
e.preventDefault()
firstError.focus()
// Announce error summary
const errorSummary = document.getElementById('error-summary')
errorSummary.textContent = `${errors.length} errors found. Please fix them and try again.`
errorSummary.focus()
}
})
兼容性
有效的 HTML(4.1.1)
<div id="content">...</div>
<div id="content">...</div>
<a href="/"><button>Click</button></a>
<div id="main-content">...</div>
<div id="sidebar-content">...</div>
<a href="/" class="button-link">Click</a>
ARIA 用法(4.1.2)
优先使用原生元素:
<div role="button" tabindex="0">Click me</div>
<button>Click me</button>
<div role="checkbox" aria-checked="false">Option</div>
<label><input type="checkbox" /> Option</label>
需要使用 ARIA 时:
<div role="tablist" aria-label="Product information">
<button role="tab" id="tab-1" aria-selected="true" aria-controls="panel-1">Description</button>
<button role="tab" id="tab-2" aria-selected="false" aria-controls="panel-2" tabindex="-1">Reviews</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
</div>
实时区域(4.1.3)
<div aria-live="polite" aria-atomic="true" class="status">
</div>
<div role="alert" aria-live="assertive">
</div>
// Announce dynamic content changes
function showNotification(message, type = 'polite') {
const container = document.getElementById(`${type}-announcer`)
container.textContent = '' // Clear first
requestAnimationFrame(() => {
container.textContent = message
})
}
测试清单
自动化测试
# Lighthouse accessibility audit
npx lighthouse https://example.com --only-categories=accessibility
# axe-core
npm install @axe-core/cli -g
axe https://example.com
手动测试
- 键盘导航: 使用 Tab 遍历整个页面,使用 Enter/Space 激活
- 屏幕阅读器: 使用 VoiceOver(Mac)、NVDA(Windows)或 TalkBack(Android)进行测试
- 缩放: 内容在 200% 缩放下仍可使用
- 高对比度: 使用 Windows 高对比度模式进行测试
- 减少动效: 使用
prefers-reduced-motion: reduce进行测试 - 焦点顺序: 符合逻辑,并与视觉顺序一致
屏幕阅读器命令
| 操作 | VoiceOver(Mac) | NVDA(Windows) |
|---|---|---|
| 启动/停止 | ⌘ + F5 | Ctrl + Alt + N |
| 下一项 | VO + → | ↓ |
| 上一项 | VO + ← | ↑ |
| 激活 | VO + Space | Enter |
| 标题列表 | VO + U,然后按方向键 | H / Shift + H |
| 链接列表 | VO + U | K / Shift + K |
按影响程度分类的常见问题
严重(立即修复)
- 缺少表单标签
- 图片缺少替代文本
- 色彩对比度不足
- 键盘陷阱
- 没有焦点指示器
重要(发布前修复)
- 缺少页面语言
- 缺少标题结构
- 链接文本描述不清
- 媒体自动播放
- 缺少跳过链接
中等(尽快修复)
- 图标缺少 ARIA 标签
- 导航不一致
- 未标识错误
- 时间限制没有控制选项
- 缺少地标区域
References
评论
支持 Markdown