accessibility by oimiragieo/agent-studio
npx skills add https://github.com/oimiragieo/agent-studio --skill accessibility审查组件结构,确保使用正确的语义化元素:
检查以下内容:
<header>、<nav>、<main>、<article>、<section>、<aside>、<footer> 代替通用的 <div><button>(而非 )广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
<div onclick><a><form>、<input>、<label><h1> 到 <h6>)示例:
<!-- ❌ 错误示例 -->
<div class="header">
<div claass="nav">
<div class="nav-item" onclick="navigate()">Home</div>
</div>
</div>
<!-- ✅ 正确示例 -->
<header>
<nav>
<a href="/">Home</a>
</nav>
</header>
仅在语义化 HTML 不足时添加 ARIA 属性:
常见模式:
| 使用场景 | ARIA 属性 | 示例 |
|---|---|---|
| 自定义按钮 | role="button", tabindex="0" | <div role="button" tabindex="0"> |
| 模态对话框 | role="dialog", aria-modal="true" | <div role="dialog" aria-modal="true"> |
| 警告 | role="alert", aria-live="assertive" | <div role="alert">Error occurred</div> |
| 标签面板 | role="tabpanel", aria-labelledby | <div role="tabpanel" aria-labelledby="tab-1"> |
规则:
<button role="button"> 是多余的)aria-labelaria-hidden="true"aria-live 区域示例:
<!-- 图标按钮需要 aria-label -->
<button aria-label="Close dialog">
<i class="icon-close" aria-hidden="true"></i>
</button>
<!-- 动态内容需要实时区域 -->
<div role="alert" aria-live="assertive">Form submitted successfully</div>
验证所有交互元素均可通过键盘访问:
要求:
焦点管理:
:focus 样式)示例:
// 模态框中的焦点捕获
function openModal(modal) {
modal.style.display = 'block';
const firstFocusable = modal.querySelector('button, input, a');
firstFocusable.focus();
trapFocus(modal); // 防止焦点移出模态框
}
function trapFocus(container) {
const focusableElements = container.querySelectorAll('button, input, select, textarea, a[href]');
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
container.addEventListener('keydown', e => {
if (e.key === 'Tab') {
if (e.shiftKey && document.activeElement === firstElement) {
lastElement.focus();
e.preventDefault();
} else if (!e.shiftKey && document.activeElement === lastElement) {
firstElement.focus();
e.preventDefault();
}
}
});
}
WCAG 2.2 新增了 9 项成功标准。验证是否符合适用的标准:
当键盘聚焦的元素滚动到视图中时,它不得被粘性页眉/页脚或重叠的 UI 完全隐藏。聚焦元素必须始终至少部分可见。
/* 防止焦点隐藏在粘性页眉后面 */
:focus-visible {
scroll-margin-top: 80px; /* 粘性页眉高度 + 缓冲 */
scroll-margin-bottom: 60px; /* 粘性页脚高度 + 缓冲 */
}
测试: 使用 Tab 键遍历所有交互元素 — 验证是否有元素完全隐藏在横幅、Cookie 通知或粘性栏后面。
聚焦的组件必须完全可见(不仅仅是部分)。粘性 UI 不得与焦点有任何重叠。
焦点指示器必须满足:面积至少为未聚焦组件周长的 2 倍 CSS 像素,并且聚焦状态与未聚焦状态之间的对比度至少为 3:1。
/* 满足 2.4.13 焦点外观(AAA 级) */
:focus-visible {
outline: 3px solid #005fcc;
outline-offset: 2px;
/* 3px 厚度 > 2px 最小值;#005fcc 在白色上 = 7.1:1 对比度 */
}
所有拖放功能必须提供单指针(点击/轻触)替代方案。无法执行精确拖拽手势的用户必须能够完成相同的任务。
<!-- 可排序列表:通过按钮提供拖拽替代方案 -->
<ul>
<li draggable="true" id="item-1">
Item 1
<button aria-label="Move Item 1 up" onclick="moveUp('item-1')">↑</button>
<button aria-label="Move Item 1 down" onclick="moveDown('item-1')">↓</button>
</li>
</ul>
<!-- 滑块:原生提供键盘替代方案 -->
<input type="range" min="0" max="100" value="50" aria-label="Volume" />
<!-- 或者:添加一个 input[type=number] 配套控件 -->
<input type="number" min="0" max="100" value="50" aria-label="Volume value" />
测试: 识别所有拖拽交互。验证每个都有非拖拽替代方案(按钮、上下文菜单、键盘快捷键)。
所有指针目标(按钮、链接、复选框、单选输入)必须至少为 24x24 CSS 像素,或者有足够的间距,使得 24x24 的区域不与其他目标重叠。
/* 确保最小目标尺寸 */
button,
a,
input[type='checkbox'],
input[type='radio'],
select {
min-width: 24px;
min-height: 24px;
}
/* 内联链接:使用内边距增加点击区域而不改变视觉尺寸 */
a {
padding: 4px 0;
}
/* 推荐:主要操作为 44x44 CSS 像素(移动端友好) */
.primary-action {
min-width: 44px;
min-height: 44px;
}
测试: 测量所有交互元素。验证没有低于 24x24 CSS 像素的(使用浏览器开发者工具元素检查器)。
如果帮助机制(联系链接、聊天小部件、电话号码、常见问题解答链接)出现在多个页面上,则它必须在页面内容中以相同的相对顺序出现。
<!-- 帮助机制必须在页面间一致出现 -->
<footer>
<nav aria-label="Help resources">
<!-- 此顺序不得在页面间改变 -->
<a href="/faq">FAQ</a>
<a href="/contact">Contact Support</a>
<a href="tel:+18005551234">1-800-555-1234</a>
</nav>
</footer>
测试: 在页面间导航。验证帮助链接/小部件每次都以相同的顺序出现。
当在同一会话/过程中再次请求相同信息时(例如,多步骤结账表单),必须自动填充或提供用户先前输入的信息以供选择。
<!-- 步骤 2:账单地址 — 从步骤 1 的送货地址预填充 -->
<fieldset>
<legend>Billing Address</legend>
<label>
<input type="checkbox" id="same-as-shipping" />
Same as shipping address
</label>
<!-- 勾选时:从送货地址字段自动填充账单地址字段 -->
</fieldset>
例外情况: 出于安全确认目的重新输入密码,从列表中选择项目。
身份验证过程不得要求用户完成认知功能测试(解决谜题、识别图像、记住/转录代码),除非提供了无障碍替代方案。
允许的替代方案:
生物识别身份验证(指纹、面部识别)
电子邮件魔法链接 / 短信一次性密码(用户无需回忆代码 — 只需复制粘贴)
通过第三方提供商的 OAuth
允许密码管理器(不要在密码字段中阻止粘贴)
<!-- 良好:允许在密码字段中粘贴 --> <input type="password" id="password" autocomplete="current-password" /> <!-- 不要添加:onpaste="return false" --> <!-- 良好:提供图像验证码的替代方案 --> <div role="group" aria-labelledby="captcha-label"> <span id="captcha-label">Verify you are human</span> <img src="captcha.png" alt="CAPTCHA challenge" /> <input type="text" aria-describedby="captcha-label" /> <a href="?audio-captcha">Use audio CAPTCHA instead</a> <a href="?email-login">Use email link instead</a> </div>测试: 识别所有身份验证步骤。验证没有步骤在无替代方法的情况下要求进行认知测试。
即使提供了替代方案,也不要求进行认知功能测试。
检查所有文本是否符合 WCAG 对比度比例:
标准:
| 文本大小 | WCAG AA | WCAG AAA |
|---|---|---|
| 正常文本(< 18pt) | 4.5:1 | 7:1 |
| 大文本(≥ 18pt 或 14pt 粗体) | 3:1 | 4.5:1 |
| UI 组件 | 3:1 | - |
工具:
示例:
/* ❌ 错误 - 对比度不足 */
.text {
color: #777;
background: #fff;
} /* 4.47:1 - 未达到 AA 级 */
/* ✅ 良好 - 对比度足够 */
.text {
color: #595959;
background: #fff;
} /* 7:1 - 达到 AAA 级 */
/* ✅ 良好 - 不要仅依赖颜色 */
.error {
color: #d00;
border-left: 4px solid #d00; /* 颜色之外的视觉指示器 */
}
.error::before {
content: '⚠️ ';
} /* 图标指示器 */
确保提供正确的屏幕阅读器体验:
图像的替代文本:
<!-- ❌ 错误 - 缺少或冗余的 alt -->
<img src="logo.png" />
<img src="decorative.png" alt="decorative image" />
<!-- ✅ 良好 -->
<img src="logo.png" alt="Company y Logo" />
<img src="decorative.png" alt="" role="presentation" />
图标按钮的 ARIA 标签:
<!-- ❌ 错误 - 屏幕阅读器无标签 -->
<button><i class="icon-delete"></i></button>
<!-- ✅ 良好 -->
<but tton aria-label="Delete item">
<i class="icon-delete" aria-hidden="true"></i>
</button>
动态内容的实时区域:
<!-- 立即播报错误 -->
<div role="alert" aria-live="assertive">Error: Invalid email address</div>
<!-- 礼貌地播报状态更新 -->
<div aria-live="polite" aria-atomic="true">Loading results... 3 of 10 loaded</div>
确保所有表单输入都正确标记和验证:
要求:
<label> 元素<fieldset> 和 <legend>aria-describedby 显示验证错误aria-required="true" 或 required 属性标记示例:
<!-- ✅ 良好的表单结构 -->
<form>
<fieldset>
<legend>Personal Information</legend>
<label for="name">Name (required)</label>
<input id="name" type="textt" required aria-required="true" aria-describedby="name-error" />
<span id="name-error" role="alert" class="error" aria-live="polite">
<!-- 错误信息在此显示 -->
</span>
<label for="email">Email</label>
<input id="email" type="email" aria-describedby="email-hint" />
<span id="email-hint" class="hint">We'll never share your email</span>
</fieldset>
</form>
记录发现的问题:
Skill({ skill: 'accessibility' });
输入:带有自定义模态框的 React 组件 输出:
Skill({ skill: 'accessibility', args: 'color-contrast' });
输入:包含颜色定义的 CSS 文件 输出:
Skill({ skill: 'accessibility', args: 'forms' });
输入:表单组件 输出:
<best_practices>
<div>(无语义含义)tabindex > 0(破坏自然的 Tab 键顺序)| 反面模式 | 问题 | 修复方案 |
|---|---|---|
<div onclick> | 无法通过键盘访问 | 使用 <button> |
| 无替代文本 | 屏幕阅读器无法描述 | 添加有意义的 alt 属性 |
| 仅依赖颜色 | 色盲用户会错过信息 | 添加文本/图标 |
| 无焦点指示器 | 用户在导航中迷失 | 添加 :focus-visible 样式 |
| 自动播放媒体 | 干扰屏幕阅读器 | 添加控制,暂停选项 |
所有内容都用 <div> | 无语义结构 | 使用语义化 HTML |
| 粘性页眉无滚动边距 | 焦点隐藏在粘性栏后面(2.4.11) | 为 :focus-visible 添加 scroll-margin-top |
| 仅支持拖拽的可排序列表 | 运动障碍用户无法排序(2.5.7) | 为每个项目添加上/下按钮 |
| 16x16px 图标按钮 | 低于 24x24 最小目标尺寸(2.5.8) | 设置 min-width: 24px; min-height: 24px |
| 无替代方案的验证码 | 身份验证的认知障碍(3.3.8) | 提供电子邮件魔法链接或通行密钥选项 |
onpaste="return false" | 阻止密码管理器粘贴(3.3.8) | 从密码字段移除粘贴阻止 |
| 结账中重复的表单字段 | 冗余数据输入(3.3.7) | 使用先前输入的值自动填充 |
在最终确定无障碍性审查之前:
WCAG 2.1 AA 级(现有要求):
alt="")WCAG 2.2 AA 级(新要求 — 2023年10月,ISO/IEC 40500:2025):
<button>、<nav>、<main> 等)之前,永远不要先考虑 ARIA。outline: none 是立即的 WCAG 失败。键盘用户会完全迷失。.claude/rules/accessibility.md - 完整的无障碍性规则开始前:
cat .claude/context/memory/learnings.md
检查:
完成后:
.claude/context/memory/learnings.md.claude/context/memory/issues.md.claude/context/memory/decisions.md假设中断:您的上下文可能会重置。如果它不在记忆中,那就没有发生过。
每周安装次数
75
代码仓库
GitHub 星标数
19
首次出现
2026年1月27日
安全审计
安装于
github-copilot73
gemini-cli72
codex71
opencode71
cursor71
kimi-cli70
Review component structure for proper semantic elements:
Check for:
<header>, <nav>, <main>, <article>, <section>, <aside>, <footer> instead of generic <div><button> for clickable elements (not <div onclick>)<a> for navigation links<form>, <input>, <label> for forms<h1> through <h6>)Example:
<!-- ❌ BAD -->
<div class="header">
<div claass="nav">
<div class="nav-item" onclick="navigate()">Home</div>
</div>
</div>
<!-- ✅ GOOD -->
<header>
<nav>
<a href="/">Home</a>
</nav>
</header>
Add ARIA attributess ONLY when semantic HTML is insufficient:
Common Patterns:
| Use Case | ARIA Attributes | Example |
|---|---|---|
| Custom button | role="button", tabindex="0" | <div role="button" tabindex="0"> |
| Modal dialog | role="dialog", aria-modal="true" | <div role="dialog" aria-modal="true"> |
| Alert | role="alert", |
Rules:
<button role="button"> is unnecessary)aria-label for icon buttons without textaria-hidden="true" for decorative elementsaria-live regions for dynamic contentExample:
<!-- Icon button needs aria-label -->
<button aria-label="Close dialog">
<i class="icon-close" aria-hidden="true"></i>
</button>
<!-- Dynamic content needs live region -->
<div role="alert" aria-live="assertive">Form submitted successfully</div>
Verify all interactive elements are keyboard accessible:
Requirements:
Focus Management:
:focus styles)Example:
// Focus trap in modal
function openModal(modal) {
modal.style.display = 'block';
const firstFocusable = modal.querySelector('button, input, a');
firstFocusable.focus();
trapFocus(modal); // Prevent escape from modal
}
function trapFocus(container) {
const focusableElements = container.querySelectorAll('button, input, select, textarea, a[href]');
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
container.addEventListener('keydown', e => {
if (e.key === 'Tab') {
if (e.shiftKey && document.activeElement === firstElement) {
lastElement.focus();
e.preventDefault();
} else if (!e.shiftKey && document.activeElement === lastElement) {
firstElement.focus();
e.preventDefault();
}
}
});
}
WCAG 2.2 added 9 new success criteria. Verify compliance with the applicable ones:
When a keyboard-focused element scrolls into view, it must not be completely hidden by sticky headers/footers or overlapping UI. At least part of the focused element must always be visible.
/* Prevent focus from hiding behind sticky headers */
:focus-visible {
scroll-margin-top: 80px; /* Height of sticky header + buffer */
scroll-margin-bottom: 60px; /* Height of sticky footer + buffer */
}
Test: Tab through all interactive elements — verify none are fully hidden behind banners, cookie notices, or sticky bars.
The focused component must be fully visible (not just partially). Sticky UI must not overlap focus at all.
Focus indicators must have: area of at least the perimeter of the unfocused component times 2 CSS pixels, and contrast ratio of at least 3:1 between focused and unfocused states.
/* Meeting 2.4.13 Focus Appearance (AAA) */
:focus-visible {
outline: 3px solid #005fcc;
outline-offset: 2px;
/* 3px thickness > 2px minimum; #005fcc on white = 7.1:1 contrast */
}
All drag-and-drop functionality MUST have a single-pointer (click/tap) alternative. Users who cannot perform precise drag gestures must be able to accomplish the same task.
<!-- Sortable list: drag alternative via buttons -->
<ul>
<li draggable="true" id="item-1">
Item 1
<button aria-label="Move Item 1 up" onclick="moveUp('item-1')">↑</button>
<button aria-label="Move Item 1 down" onclick="moveDown('item-1')">↓</button>
</li>
</ul>
<!-- Slider: keyboard alternative provided natively -->
<input type="range" min="0" max="100" value="50" aria-label="Volume" />
<!-- Alternatively: add an input[type=number] companion -->
<input type="number" min="0" max="100" value="50" aria-label="Volume value" />
Test: Identify all drag interactions. Verify each has a non-drag equivalent (buttons, context menus, keyboard shortcuts).
All pointer targets (buttons, links, checkboxes, radio inputs) must be at least 24x24 CSS pixels , OR have sufficient spacing so the 24x24 area does not overlap another target.
/* Ensure minimum target size */
button,
a,
input[type='checkbox'],
input[type='radio'],
select {
min-width: 24px;
min-height: 24px;
}
/* Inline links: use padding to increase hit area without visual size change */
a {
padding: 4px 0;
}
/* Recommended: 44x44 CSS pixels for primary actions (mobile-friendly) */
.primary-action {
min-width: 44px;
min-height: 44px;
}
Test: Measure all interactive elements. Verify none fall below 24x24 CSS pixels (use browser DevTools element inspector).
If a help mechanism (contact link, chat widget, phone number, FAQ link) appears on multiple pages, it must appear in the same relative order in the page content.
<!-- Help mechanism must appear consistently across pages -->
<footer>
<nav aria-label="Help resources">
<!-- This order must not change between pages -->
<a href="/faq">FAQ</a>
<a href="/contact">Contact Support</a>
<a href="tel:+18005551234">1-800-555-1234</a>
</nav>
</footer>
Test: Navigate between pages. Verify help links/widgets appear in the same order each time.
Information previously entered by the user must be auto-populated or available for selection when the same information is requested again in the same session/process (e.g., multi-step checkout forms).
<!-- Step 2: Billing address — pre-fill from Step 1 shipping -->
<fieldset>
<legend>Billing Address</legend>
<label>
<input type="checkbox" id="same-as-shipping" />
Same as shipping address
</label>
<!-- When checked: auto-populate billing fields from shipping fields -->
</fieldset>
Exceptions: Re-entering passwords for security confirmation, selecting items from a list.
Authentication processes MUST NOT require users to complete a cognitive function test (solve puzzle, identify images, remember/transcribe a code) unless an accessible alternative is provided.
Allowed alternatives:
Biometric authentication (fingerprint, face recognition)
Email magic link / SMS OTP (user does not need to recall the code — just copy-paste)
OAuth via a third-party provider
Password managers allowed (do not block paste in password fields)
<!-- GOOD: Allow paste in password fields --> <input type="password" id="password" autocomplete="current-password" /> <!-- Do NOT add: onpaste="return false" --> <!-- GOOD: Provide alternative to image CAPTCHA --> <div role="group" aria-labelledby="captcha-label"> <span id="captcha-label">Verify you are human</span> <img src="captcha.png" alt="CAPTCHA challenge" /> <input type="text" aria-describedby="captcha-label" /> <a href="?audio-captcha">Use audio CAPTCHA instead</a> <a href="?email-login">Use email link instead</a> </div>Test: Identify all authentication steps. Verify no step requires a cognitive test without an alternative method.
No cognitive function test is required, even with alternatives provided.
Check all text meets WCAG contrast ratios:
Standards:
| Text Size | WCAG AA | WCAG AAA |
|---|---|---|
| Normal text (< 18pt) | 4.5:1 | 7:1 |
| Large text (≥ 18pt or 14pt bold) | 3:1 | 4.5:1 |
| UI components | 3:1 | - |
Tools:
Example:
/* ❌ BAD - Innsufficient contrast */
.text {
color: #777;
background: #fff;
} /* 4.47:1 - fails AA */
/* ✅ GOOD - Sufficient contrast */
.text {
color: #595959;
background: #fff;
} /* 7:1 - passes AAA */
/* ✅ GOOD - Don't rely on color alone */
.error {
color: #d00;
border-left: 4px solid #d00; /* Visual indicator beyond color */
}
.error::before {
content: '⚠️ ';
} /* Icon indicator */
Ensure proper sscreen reader experience:
Alt Text for Images:
<!-- ❌ BAD - Missing or redundant alt -->
<img src="logo.png" />
<img src="decorative.png" alt="decorative image" />
<!-- ✅ GOOD -->
<img src="logo.png" alt="Company y Logo" />
<img src="decorative.png" alt="" role="presentation" />
ARIA Labels for Icon Buttons:
<!-- ❌ BAD - No label for screen readers -->
<button><i class="icon-delete"></i></button>
<!-- ✅ GOOD -->
<but tton aria-label="Delete item">
<i class="icon-delete" aria-hidden="true"></i>
</button>
Live Regions for Dynamic Content:
<!-- Announce errors immediately -->
<div role="alert" aria-live="assertive">Error: Invalid email address</div>
<!-- Announce status updates politely -->
<div aria-live="polite" aria-atomic="true">Loading results... 3 of 10 loaded</div>
Ensure all form inputs are properly labeled and validated:
Requirements:
<label> elements<fieldset> and <legend> for grouped inputsaria-describedbyaria-required="true" or required attributeExample:
<!-- ✅ GOOD Form Structure -->
<form>
<fieldset>
<legend>Personal Information</legend>
<label for="name">Name (required)</label>
<input id="name" type="textt" required aria-required="true" aria-describedby="name-error" />
<span id="name-error" role="alert" class="error" aria-live="polite">
<!-- Error message appears here -->
</span>
<label for="email">Email</label>
<input id="email" type="email" aria-describedby="email-hint" />
<span id="email-hint" class="hint">We'll never share your email</span>
</fieldset>
</form>
Document findings with:
Skill({ skill: 'accessibility' });
Input : React component with custom modal Output :
Skill({ skill: 'accessibility', args: 'color-contrast' });
Input : CSS file with color definitions Output :
Skill({ skill: 'accessibility', args: 'forms' });
Input : Form component Output :
<best_practices>
<div> for everything (no semantic meaning)tabindex > 0 (disrupts natural tab order)| Anti-Pattern | Problem | Fix |
|---|---|---|
<div onclick> | Not keyboard accessible | Use <button> |
| No alt text | Screen readers can't describe | Add meaningful alt attribute |
| Color-only info | Color blind users miss it | Add text/icons |
| No focus indicators | Users lost in navigation | Add :focus-visible styles |
| Auto-play media | Disruptive for screen readers | Add controls, pause option |
| for everything |
Before finalizing accessibility review:
WCAG 2.1 AA (existing requirements):
alt="" for decorative)WCAG 2.2 AA (new requirements — October 2023, ISO/IEC 40500:2025):
<button>, <nav>, <main>, etc.).outline: none without a replacement is an immediate WCAG failure. Keyboard users become completely lost..claude/rules/accessibility.md - Complete accessibility rulesBefore starting:
cat .claude/context/memory/learnings.md
Check for:
After completing:
.claude/context/memory/learnings.md.claude/context/memory/issues.md.claude/context/memory/decisions.mdASSUME INTERRUPTION: Your context may reset. If it's not in memory, it didn't happen.
Weekly Installs
75
Repository
GitHub Stars
19
First Seen
Jan 27, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
github-copilot73
gemini-cli72
codex71
opencode71
cursor71
kimi-cli70
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
120,000 周安装
aria-live="assertive"<div role="alert">Error occurred</div> |
| Tab panel | role="tabpanel", aria-labelledby | <div role="tabpanel" aria-labelledby="tab-1"> |
<div>| No semantic structure |
| Use semantic HTML |
| Sticky header without scroll-margin | Focus hidden behind sticky bar (2.4.11) | Add scroll-margin-top to :focus-visible |
| Drag-only sortable lists | Users with motor disabilities can't sort (2.5.7) | Add Up/Down buttons for each item |
| 16x16px icon buttons | Below 24x24 minimum target size (2.5.8) | Set min-width: 24px; min-height: 24px |
| CAPTCHA without alternative | Cognitive barrier to authentication (3.3.8) | Provide email magic link or passkey option |
onpaste="return false" | Blocks password manager paste (3.3.8) | Remove paste block from password fields |
| Repeated form fields in checkout | Redundant data entry (3.3.7) | Auto-populate with previously entered values |