关于 web-accessibility
面向 Web 开发者的无障碍设计技能,提供语义 HTML、键盘操作、ARIA、表单、触控和国际化等实现与审查指导。还包含检查清单,便于系统性检查界面。
主要功能
- 01选择语义化 HTML 元素
- 02支持键盘导航并保持焦点可见
- 03使用 ARIA 标注动态内容
- 04为表单提供标签与错误关联
- 05检查触控目标和国际化格式
- 06通过清单审查无障碍实现
适用场景
- 01设计可键盘操作的交互界面
- 02检查表单标签与错误提示
- 03为动态通知添加屏幕阅读器支持
- 04审查移动端触控与焦点样式
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ s-hiraoku/ synapse-a2a --skill web-accessibility安装后在 Claude Code 中输入/web-accessibility确认已加载。
详细介绍
Web 无障碍
构建人人都能使用的界面。这些不是可有可无的增强功能,而是基础质量要求。
语义化 HTML
为每项任务使用正确的元素。绝不要用 <div> 模拟交互元素。
// 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>
元素选择指南
| 用途 | 元素 | 不要使用 |
|---|---|---|
| 执行操作(提交、切换、删除) | <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"> |
键盘导航
每个交互元素都必须支持键盘访问。
焦点管理
/* 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);
}
键盘事件处理
// 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
跳过链接
为键盘用户提供跳过导航。
<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>
作为滚动目标的标题需要为固定页眉设置偏移:
[id] { scroll-margin-top: 5rem; }
ARIA 模式
图标按钮
// 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
实时区域
向屏幕阅读器播报动态内容变化。
// 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>
加载状态
<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>
表单
标签
每个输入框都必须关联一个标签。
// 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 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" />
验证和错误
<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>
表单行为规则
- 绝不阻止粘贴到任何输入框
- 对电子邮件地址和代码禁用拼写检查:
spellCheck={false} - 请求开始前保持提交按钮可用;加载时显示旋转指示器
- 提交失败时将焦点移至第一个错误
- 复选框和单选按钮:点击目标区域应为一个整体,标签与输入框之间不能有无法点击的区域
图片和媒体
// 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="..." />
触控和移动设备
触控目标
所有交互元素的最小尺寸为 44x44px(WCAG 2.5.5)。
.touch-target {
min-height: 44px;
min-width: 44px;
}
安全区域
适配设备刘海和主屏幕指示条。
.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);
}
触控行为
/* Prevent 300ms delay and highlight flash */
.interactive {
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
}
/* Prevent scroll chaining on modals */
.modal { overscroll-behavior: contain; }
国际化
// 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';
提升无障碍性能
- 列表超过 50 项时:使用虚拟化
- 关键字体:使用带有
font-display: swap的<link rel="preload" as="font"> - 避免在渲染期间读取布局(这也会导致屏幕阅读器用户体验卡顿)
- 对大型表单而言,非受控输入的性能优于受控输入
检查清单
用此清单进行检查:
- 所有交互元素都可通过键盘访问
-
:focus-visible状态下焦点指示器清晰可见 - 颜色对比度符合 WCAG AA(正文 4.5:1,大号文本 3:1)
- 图片具有适当的替代文本
- 表单输入框具有标签
- 错误消息通过
aria-describedby与输入框关联 - 仅含图标的按钮具有
aria-label - 动态内容使用
aria-live区域 - 触控目标的最小尺寸为 44x44px
- 遵循
prefers-reduced-motion设置 - 不使用没有替代焦点样式的
outline: none - 标题层级按顺序排列(不跳过级别)
评论
支持 Markdown