ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Web 无障碍合规实战:基于 accessibility-compliance 技能的 WCAG 2.2 模式与实现详解

2026/9/10 22:48:51 拓冰建站 浏览量
Web 无障碍合规实战:基于 accessibility-compliance 技能的 WCAG 2.2 模式与实现详解 Web 无障碍合规实战基于 accessibility-compliance 技能的 WCAG 2.2 模式与实现详解【免费下载链接】agentsMulti-harness agentic plugin marketplace for Claude Code, Codex, Cursor, OpenCode, GitHub Copilot, and Google Antigravity项目地址: https://gitcode.com/GitHub_Trending/agents24/agents本篇技术指南以 accessibility-compliance 技能的详细模式文档 为骨架系统讲解 WCAG 2.2 无障碍实现的核心能力、成功标准、可复用的组件模式按钮、模态框、表单、跳转链接、活动区域以及颜色对比度计算工具。你在读完本文后将能够按照 WCAG 2.2 的 POUR 原则审查界面、直接落地 5 个开箱即用的无障碍组件代码模式、用标准算法计算并校验颜色对比度并结合本仓库配套的审计命令与屏幕阅读器测试技能完成从实现到验证的完整闭环。技能定位谁在使用这份文档在 ui-design 插件的 accessibility-compliance 技能 中本明细文档被定位为“导航层之上、深入落地时必读”的实现手册——SKILL.md 原文指明“Detailed pattern documentation lives inreferences/details.md. Read that file when the navigation tier above is insufficient.”详细模式文档位于 references/details.md当上层导航不足以支撑实现时阅读该文件。也就是说这份details.md面向的是已经明确要动手写代码的场景实现 WCAG 2.2 A/AA/AAA 合规界面、构建屏幕阅读器可访问界面、为交互组件添加键盘导航、实现焦点管理与焦点陷阱、创建带正确标签的可访问表单等。仓库中还配套了三个支撑性参考文档WCAG 2.2 指南参考按 POUR 四大原则逐条展开成功标准与对应代码示例ARIA 模式与最佳实践手写 Accordion、Tabs、Menu Button、Combobox 等复杂组件移动端无障碍VoiceOver、TalkBack、触控目标与文本缩放。另外accessibility-compliance 插件的 accessibility-audit 命令 与 screen-reader-testing 技能 提供了与本文模式相对应的自动化审计与人工验证手段供实现后回归测试使用。五大核心能力概览details.md开篇将无障碍实现归纳为五个能力域这也是后续所有模式的设计出发点能力域关注点WCAG 2.2 指南四大原则 POURPerceivable可感知、Operable可操作、Understandable可理解、Robust健壮ARIA 模式Roles角色、States状态、Properties属性、Live regions活动区域键盘导航焦点顺序与 Tab 序列、可见焦点指示、快捷键、模态框焦点陷阱屏幕阅读器支持语义化 HTML、图片替代文本、标题层级、跳过链接与地标移动端无障碍触控目标尺寸最低 44×44dp、VoiceOver/TalkBack 兼容、手势替代方案、Dynamic Type 支持其中 WCAG 2.2 的四大原则可进一步理解为wcag-guidelines.md 指出内容必须“以不同方式呈现”“可用键盘和辅助技术导航”“内容与操作清晰明确”“能与当前及未来的辅助技术协作”。而 ARIA 四要素在 aria-patterns.md 中有完整展开角色定义元素用途button、dialog、navigation状态指示当前条件expanded、selected、disabled属性描述关系labelledby、describedby活动区域用于播报动态内容变化。WCAG 2.2 成功标准速查表下表是details.md提供的 WCAG 2.2 成功标准清单是审计与实现的直接依据。注意其中 2.5.8 Target Size (Minimum) 是 WCAG 2.2 新增的 AA 级标准级别标准说明A1.1.1非文本内容必须有文本替代A1.3.1信息与关系必须可编程判定A2.1.1所有功能均可键盘操作A2.4.1提供跳过主要内容机制AA1.4.3文本对比度 4.5:1大文本 3:1AA1.4.11非文本对比度 3:1AA2.4.7焦点必须可见AA2.5.8目标尺寸最小 24×24px2.2 新增AAA1.4.6增强对比度 7:1AAA2.5.5目标尺寸最小 44×44px配合 wcag-audit-patterns 技能 中的分级模型理解这组标准Level A 是法定底线对应 ADA/Section 508 等法规要求Level AA 是多数监管标准的目标等级Level AAA 面向特殊需求场景。实践中绝大多数组织以 AA 为合规目标。在实际审计中可依据影响程度对常见违规分级源自 wcag-audit-patterns 技能Critical阻断级: ├── 功能性图片缺少 alt 文本 ├── 交互元素无法键盘访问 ├── 表单缺少标签 └── 自动播放媒体且无控制 Serious严重: ├── 颜色对比度不足 ├── 缺少跳过链接 ├── 自定义组件不可访问 └── 缺少页面标题 Moderate中等: ├── 缺少 lang 属性 ├── 链接文本不明确 ├── 缺少地标landmarks └── 标题层级混乱模式一可访问按钮Accessible Buttondetails.md给出的第一个模式解决的是加载状态按钮的无障碍问题。核心要点有三个禁用态同步给辅助技术、加载态播报、触控目标尺寸达标interface ButtonProps extends React.ButtonHTMLAttributesHTMLButtonElement { variant?: primary | secondary; isLoading?: boolean; } function AccessibleButton({ children, variant primary, isLoading false, disabled, ...props }: ButtonProps) { return ( button // Disable when loading disabled{disabled || isLoading} // Announce loading state to screen readers aria-busy{isLoading} // Describe the buttons current state aria-disabled{disabled || isLoading} className{cn( // Visible focus ring focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2, // Minimum touch target size (44x44px) min-h-[44px] min-w-[44px], variant primary bg-primary text-primary-foreground, (disabled || isLoading) opacity-50 cursor-not-allowed, )} {...props} {isLoading ? ( span classNamesr-onlyLoading/span Spinner aria-hiddentrue / / ) : ( children )} /button ); }值得逐行解读的设计决策aria-busy{isLoading}将异步加载状态暴露给辅助技术屏幕阅读器可感知按钮“忙碌”aria-disabled与disabled同时使用原生disabled阻止交互aria-disabled则把语义暴露在无障碍树中focus-visible:ring-2不删除焦点样式而是重新设计焦点样式。这与 SKILL.md 最佳实践第 4 条“Dont Disable Focus Styles: Style them, dont remove them”完全一致也对应 WCAG 2.4.7 Focus VisibleAAmin-h-[44px] min-w-[44px]满足 WCAG 2.2 AAA 级 2.5.5 的 44×44 推荐值同时高于 AA 级 2.5.8 的 24×24 底线加载时用sr-only的“Loading”文本配合aria-hidden的 Spinner视觉用户看到旋转图标屏幕阅读器用户听到文本播报两者各得其所。如果组件是纯图标按钮wcag-guidelines.md 中 1.1.1 给出了补充写法button aria-labelDelete itemTrashIcon aria-hiddentrue //button即图标本身从无障碍树中隐藏由aria-label提供可访问名称。模式二可访问模态对话框Accessible Modal Dialog模态框是无障碍实现的高危区问题集中在焦点陷阱、Esc 关闭、背景滚动三个点。details.md的第二个模式完整覆盖了这些import * as React from react; import { FocusTrap } from headlessui/react; interface DialogProps { isOpen: boolean; onClose: () void; title: string; children: React.ReactNode; } function AccessibleDialog({ isOpen, onClose, title, children }: DialogProps) { const titleId React.useId(); const descriptionId React.useId(); // Close on Escape key React.useEffect(() { const handleKeyDown (e: KeyboardEvent) { if (e.key Escape isOpen) { onClose(); } }; document.addEventListener(keydown, handleKeyDown); return () document.removeEventListener(keydown, handleKeyDown); }, [isOpen, onClose]); // Prevent body scroll when open React.useEffect(() { if (isOpen) { document.body.style.overflow hidden; } return () { document.body.style.overflow ; }; }, [isOpen]); if (!isOpen) return null; return ( div roledialog aria-modaltrue aria-labelledby{titleId} aria-describedby{descriptionId} {/* Backdrop */} div classNamefixed inset-0 bg-black/50 aria-hiddentrue onClick{onClose} / {/* Focus trap container */} FocusTrap div classNamefixed inset-0 flex items-center justify-center p-4 div classNamebg-background rounded-lg shadow-lg max-w-md w-full p-6 h2 id{titleId} classNametext-lg font-semibold {title} /h2 div id{descriptionId}{children}/div button onClick{onClose} classNameabsolute top-4 right-4 aria-labelClose dialog X classNameh-4 w-4 / /button /div /div /FocusTrap /div ); }关键无障碍要素命名与描述aria-labelledby{titleId}将h2标题作为对话框的可访问名称aria-describedby{descriptionId}关联正文描述对应 4.1.2 Name, Role, ValueA 级焦点管理FocusTrap保证 Tab 键在对话框内部循环2.1.2 No Keyboard Trap背景层的aria-hiddentrue防止辅助技术读到背板Esc 关闭全局keydown监听实现 2.1.2 的“可无键盘陷阱地退出”也符合 SKILL.md 常见问题清单中“Keyboard traps”的排查方向关闭按钮aria-labelClose dialog为纯图标按钮提供可访问名称避免屏幕阅读器只读出“X”。值得补充的是焦点归还逻辑wcag-guidelines.md 的 2.1.2 模式记录了关闭时恢复previousFocus关闭前焦点元素的 useEffect 写法screen-reader-testing 技能 的通用测试场景中也给出了手写trapFocus函数的完整实现含 ShiftTab 反向循环与 Esc 关闭分支适合不依赖 Headless UI 的场景。测试要点是打开模态框后焦点应移入、Tab 不应逃逸到背景、关闭后焦点应返回触发元素。模式三可访问表单Accessible Form表单模式聚焦错误提示的双通道传达——既要可见又要被屏幕阅读器播报function AccessibleForm() { const [errors, setErrors] React.useStateRecordstring, string({}); return ( form aria-describedbyform-errors noValidate {/* Error summary for screen readers */} {Object.keys(errors).length 0 ( div idform-errors rolealert aria-liveassertive classNamebg-destructive/10 border border-destructive p-4 rounded-md mb-4 h2 classNamefont-semibold text-destructive Please fix the following errors: /h2 ul classNamelist-disc list-inside mt-2 {Object.entries(errors).map(([field, message]) ( li key{field} a href{#${field}} classNameunderline {message} /a /li ))} /ul /div )} {/* Required field with error */} div classNamespace-y-2 label htmlForemail classNameblock font-medium Email address span aria-hiddentrue classNametext-destructive ml-1 * /span span classNamesr-only(required)/span /label input idemail nameemail typeemail required aria-requiredtrue aria-invalid{!!errors.email} aria-describedby{errors.email ? email-error : email-hint} className{cn( w-full px-3 py-2 border rounded-md, errors.email border-destructive, )} / {errors.email ? ( p idemail-error classNametext-sm text-destructive rolealert {errors.email} /p ) : ( p idemail-hint classNametext-sm text-muted-foreground Well never share your email. /p )} /div button typesubmit classNamemt-4 Submit /button /form ); }表单无障碍的六个要素错误汇总区rolealertaria-liveassertive会让错误列表立即打断当前播报3.3.1 Error IdentificationA 级且每条错误是锚点链接可直达对应输入框必填标记视觉星号加aria-hiddentrue避免读作“star”同时提供sr-only的“(required)”文本屏幕阅读器读“Email address (required)”关联错误aria-invalid{!!errors.email}标记字段无效aria-describedby动态切换到错误或提示文本的 ID双通道提示错误时输入框加border-destructive红色边框但颜色不是唯一指示——同时有rolealert的文本与aria-invalid状态符合 1.4.1 Use of ColorA 级与 wcag-guidelines.md 中“Color plus icon and text”的推荐做法提示与错误分离email-hint提示与email-error错误用同一aria-describedby槽位二选一避免屏幕阅读器一次读出过多文本noValidate关闭浏览器默认校验气泡改用自定义可控的错误展示保证跨浏览器行为一致。wcag-guidelines.md 还补充了1.3.5 Identify Input PurposeAA的字段语义化为姓名、邮箱、电话、地址、信用卡号等输入添加autoComplete属性如autoCompleteemail、autoCompletecc-number让浏览器与辅助技术能正确自动填充以及3.3.7 Redundant EntryA 级2.2 新增的“账单地址即收货地址”勾选复用模式。模式四跳过导航链接Skip Navigation Link2.4.1 Bypass BlocksA 级要求提供绕过重复内容的机制。details.md的实现采用“仅焦点可见”的经典做法——平时sr-only隐藏聚焦时以绝对定位浮现function SkipLink() { return ( a href#main-content className{cn( // Hidden by default, visible on focus sr-only focus:not-sr-only, focus:absolute focus:top-4 focus:left-4 focus:z-50, focus:bg-background focus:px-4 focus:py-2 focus:rounded-md, focus:ring-2 focus:ring-primary, )} Skip to main content /a ); } // In layout function Layout({ children }) { return ( SkipLink / header.../header nav aria-labelMain navigation.../nav main idmain-content tabIndex{-1} {children} /main footer.../footer / ); }三个细节值得注意sr-only focus:not-sr-only视觉上隐藏但保留在无障碍树中聚焦后变为可见避免干扰正常布局main idmain-content tabIndex{-1}目标锚点需要tabIndex{-1}才能接收编程焦点跳到main时把焦点真正移入内容区而非仅仅滚动nav aria-labelMain navigation为导航地标提供可访问名称与 screen-reader-testing 技能 中“Landmarks properly labeled”的检查项对应。wcag-guidelines.md 的 2.4.1 给出了可同时提供“Skip to main content”与“Skip to navigation”两个链接的扩展版本适合头部导航极其复杂的站点。模式五活动区域播报Live Region for Announcements动态内容更新是屏幕阅读器最容易“静默失败”的场景。details.md的最后一个模式用一个useAnnounceHook 封装了播报逻辑function useAnnounce() { const [message, setMessage] React.useState(); const announce React.useCallback( (text: string, priority: polite | assertive polite) { setMessage(); // Clear first to ensure re-announcement setTimeout(() setMessage(text), 100); }, [], ); const Announcer () ( div rolestatus aria-livepolite aria-atomictrue classNamesr-only {message} /div ); return { announce, Announcer }; } // Usage function SearchResults({ results, isLoading }) { const { announce, Announcer } useAnnounce(); React.useEffect(() { if (!isLoading results) { announce(${results.length} results found); } }, [results, isLoading, announce]); return ( Announcer / ul{/* results */}/ul / ); }实现要点setMessage()后延迟 100ms 再设置先清空再赋值强制触发内容变更保证相同文本也能重复播报aria-livepolite不打断当前播报等读完后插入适合“搜索到 N 条结果”这类非紧急信息aria-atomictrue整块内容整体播报而不是只读新增部分sr-only播报区域对视觉用户不可见仅存在于无障碍树中。aria-patterns.md 的 Live Regions 章节对优先级做了更细的划分polite 播报rolestatus适合搜索状态、进度百分比如“Loading: 75% complete”assertive 播报rolealert立即打断当前播报适合错误信息与表单校验汇总log 区域rolelogaria-relevantadditions只播报新增项、不播报移除项适合聊天消息流与活动日志。颜色对比度WCAG 要求与计算工具details.md在模式之后给出了完整的对比度工具代码与 WCAG 要求常量表这是审计自动化的核心算法// Contrast ratio utilities function getContrastRatio(foreground: string, background: string): number { const fgLuminance getLuminance(foreground); const bgLuminance getLuminance(background); const lighter Math.max(fgLuminance, bgLuminance); const darker Math.min(fgLuminance, bgLuminance); return (lighter 0.05) / (darker 0.05); } // WCAG requirements const CONTRAST_REQUIREMENTS { // Normal text (18pt or 14pt bold) normalText: { AA: 4.5, AAA: 7, }, // Large text (18pt or 14pt bold) largeText: { AA: 3, AAA: 4.5, }, // UI components and graphics uiComponents: { AA: 3, }, };对比度公式解读(较亮亮度 0.05) / (较暗亮度 0.05)其中亮度指相对亮度relative luminance。完整的相对亮度计算对 sRGB 通道做伽马解码后按 0.2126/0.7152/0.0722 加权可参考 accessibility-audit 命令 中ColorContrastAnalyzer的relativeLuminance实现relativeLuminance(rgb) { const [r, g, b] rgb.map(val { val val / 255; return val 0.03928 ? val / 12.92 : Math.pow((val 0.055) / 1.055, 2.4); }); return 0.2126 * r 0.7152 * g 0.0722 * b; }对照速查与文首的 WCAG 2.2 清单表一一对应| 文本类别 | 判定条件 | AA | AAA | | -------- | -------- | -- | --- | | 普通文本 | 18pt 或 14pt 粗体 | 4.5:1 | 7:1 | | 大文本 | ≥ 18pt 或 ≥ 14pt 粗体 | 3:1 | 4.5:1 | | UI 组件/图形 | 边界、图标、焦点指示等非文本 | 3:1 | — |补充 WCAG 1.4.11 Non-text ContrastAA输入框边框、自定义复选框、焦点轮廓等 UI 组件与相邻背景的对比度需达到 3:1wcag-guidelines.md 中给出了border: 2px solid #767676白色背景上约 3:1等可直接套用的样式。同时prefers-contrast: high媒体查询见 accessibility-audit 命令 的高对比 CSS 示例可用于在高对比模式下强制加深边框与下划线。实现之后自动化审计与回归验证details.md的模式都遵循一个原则——实现只是开始验证才证明合规。本仓库为此提供了完整的验证链1. 自动化扫描axe-core jest-axeaccessibility-audit 命令 给出了基于axe-core/puppeteer的全页审计类支持按wcag2a / wcag2aa / wcag21a / wcag21aa标签扫描、用.no-a11y-check排除节点并按critical:10 / serious:5 / moderate:2 / minor:1加权计算 100 分制的可访问性评分。组件级回归则用jest-axe的toHaveNoViolations()断言import { render } from testing-library/react; import { axe, toHaveNoViolations } from jest-axe; expect.extend(toHaveNoViolations); describe(Accessibility Tests, () { it(should have no violations, async () { const { container } render(MyComponent /); const results await axe(container); expect(results).toHaveNoViolations(); }); });2. 键盘导航与屏幕阅读器人工测试自动化只能发现约 30%–50% 的问题wcag-audit-patterns 技能 明确“Dont rely only on automated testing”键盘与屏幕阅读器测试必须人工完成键盘测试Tab 遍历所有交互元素、焦点顺序与视觉顺序一致、焦点指示始终可见、无键盘陷阱、Esc 关闭模态框、Enter/Space 激活按钮对应文首“键盘导航”能力域屏幕阅读器测试screen-reader-testing 技能 给出了 VoiceOvermacOS、NVDA/JAWSWindows、TalkBackAndroid的操作矩阵与完整测试脚本例如 NVDA 下用H遍历标题、D遍历地标、InsertF7打开元素列表核对标题层级并验证动态内容的rolestatus/rolealert是否被播报视觉测试文本对比度 ≥ 4.5:1、UI 组件对比度 ≥ 3:1、200% 缩放可用、文本间距调整不丢内容、焦点指示可见、颜色不单独承载信息。3. CI/CD 集成accessibility-audit 命令 提供了完整的 GitHub Actions 工作流示例npm ci构建后启动本地服务运行npm run test:a11yaxe 测试与npx pa11y http://localhost:3000 --standard WCAG2AA --threshold 0以 WCAG 2.0 AA 为标准、零违规阈值最后把 HTML 报告作为构建产物上传。这使无障碍回归与普通单测一样成为持续集成的固定环节。常见问题与最佳实践汇总综合 details.md、SKILL.md 与 wcag-audit-patterns 技能将高频问题与应对策略整理如下常见问题应对策略对应标准/模式图片缺少 alt 文本功能图片给描述性 alt装饰图片altrolepresentation复杂图片配aria-describedby长描述1.1.1模式一颜色对比度不足按对比度公式校验普通文本 ≥4.5:1大文本 ≥3:11.4.3对比度工具键盘陷阱模态框内使用 FocusTrapEsc 可关闭关闭后焦点归还2.1.2模式二表单缺标签每个输入关联label htmlFor或aria-label错误用aria-invalidrolealert双通道提示3.3.1模式三动态内容不播报用rolestatus/rolealert活动区域先清空再赋值确保重复播报模式五自定义控件不可访问优先语义化 HTML必须自定义时补全 role/state/value 与键盘交互4.1.2焦点顺序错乱Tab 顺序与视觉顺序一致模态框/下拉内用方向键导航roving tabindex2.4.3aria-patterns与此对应的八条最佳实践源自 SKILL.mdUse Semantic HTML能用的原生元素优先于 ARIATest with Real Users邀请残障用户参与测试Keyboard First交互设计先保证无鼠标可用Dont Disable Focus Styles样式化焦点而非删除Provide Text Alternatives所有非文本内容都有描述Support Zoom内容在 200% 缩放下可用Announce Changes动态内容用活动区域播报Respect Preferences尊重prefers-reduced-motion与prefers-contrast。其中第 1 条呼应 aria-patterns.md 的“ARIA 第一法则”不要使用 ARIA除非原生 HTML 无法胜任。该文档还专门列出了三类常见 ARIA 反模式冗余 ARIAbutton rolebutton、无效 ARIAaria-selected用在无可选角色的元素上、隐藏内容仍被播报应使用原生hidden属性而非仅display:none配合aria-hidden的混淆写法。结语从 details.md 的五种组件模式出发配合仓库内 wcag-guidelines.md 的逐条标准、aria-patterns.md 的复杂控件、mobile-accessibility.md 的移动端方案以及 accessibility-audit 命令 的自动化审计能力你可以建立“实现—扫描—人工验证—CI 回归”的完整无障碍工作流。记住无障碍不是一次性的修复任务而是贯穿设计、实现与验证各阶段的持续实践——键盘优先、语义优先、对比度与焦点可见性常驻心中最终交付的是对所有人都可用的体验。【免费下载链接】agentsMulti-harness agentic plugin marketplace for Claude Code, Codex, Cursor, OpenCode, GitHub Copilot, and Google Antigravity项目地址: https://gitcode.com/GitHub_Trending/agents24/agents创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考