无障碍设计最容易忽略的 8 个细节:从开发视角的系统化补课

📅 2026/7/27 11:32:06
无障碍设计最容易忽略的 8 个细节:从开发视角的系统化补课
无障碍设计最容易忽略的 8 个细节从开发视角的系统化补课一、引子那个键盘无法操作的下拉菜单一个用divonClick实现的精美下拉菜单鼠标操作流畅触摸操作正常——但用 Tab 键时焦点直接跳过了它。因为div默认不在焦点序列中。屏幕阅读器用户尝试操作这个菜单时VoiceOver 朗读的是空白区域。这就是无障碍设计中最典型的开发视角盲区我们把看起来没问题等同于所有人都能用。实际上无障碍问题通常不在视觉层而在交互层和语义层——键盘可访问性、屏幕阅读器支持、焦点管理、颜色对比度。以下 8 个细节是我在过去半年的无障碍审计中反复踩到的坑。它们不是 WCAG 规范的大标题而是实践中真正让用户受阻的微观问题。二、8 个容易被忽略的无障碍细节细节 1Modal 的焦点陷阱打开弹窗后按 Tab 键应该在弹窗内的元素间循环不应该跑到弹窗背后的页面元素上。实际实现中focus-trap最容易出问题的地方是弹窗内第一个元素和最后一个元素之间的循环——当在最后一个元素上按 Tab 时焦点应该回到第一个元素而不是跳出弹窗。检查方法打开一个弹窗只使用 Tab 和 ShiftTab 操作确认焦点始终在弹窗内循环。同时检查 Esc 键是否能关闭弹窗关闭后焦点是否回到触发弹窗的按钮上。细节 2路由切换后的焦点位置SPA 路由切换后页面内容变了但焦点仍然停在触发导航的链接上。屏幕阅读器用户不知道页面已经变了继续朗读链接关于我们。正确的做法是路由切换后将焦点移到新页面的主标题h1上。// 路由切换后自动管理焦点 router.afterEach(() { const mainHeading document.querySelector(h1); if (mainHeading mainHeading instanceof HTMLElement) { mainHeading.setAttribute(tabindex, -1); mainHeading.focus(); } });细节 3跳过导航链接一个有 50 个导航项的网站键盘用户按 50 次 Tab 才能看到正文。跳过链接Skip Link是第一个可聚焦元素允许直接跳到主要内容。a href#main-content classskip-link 跳到主要内容 /a style .skip-link { position: absolute; top: -100%; left: 0; z-index: 9999; padding: 8px 16px; background: #1976D2; color: white; } .skip-link:focus { top: 0; } /style细节 4用 div 模拟按钮div onClick{handleClick}点击我/div—— 这是使用率最高的反模式。div 没有rolebutton、没有tabindex0、不支持 Enter/Space 键激活。至少 30% 的键盘和屏幕阅读器用户无法操作这个元素。正确做法如果语义上是按钮就用button。如果确实需要用 div极少情况补齐所有缺失的能力div rolebutton tabindex0 aria-pressedfalse onKeyDown{(e) { if (e.key Enter || e.key ) handleClick(); }} 点击我 /div细节 5纯图标按钮没有文本标签一个只有 SVG 图标没有文字的按钮屏幕阅读器朗读的是按钮或未标记按钮。用户不知道这个按钮是做什么的。正确做法图标按钮必须提供aria-label。button aria-label关闭对话框 svg!-- X 图标 --/svg /button细节 6仅用颜色传达信息红色边框表示输入错误绿色边框表示输入正确——这是典型的仅颜色编码。色盲用户约占男性 8%无法区分红色和绿色。正确做法颜色 图标 文字三重编码。div classinput-error rolealert span aria-hiddentrue⚠️/span span密码长度不能少于 8 位/span /div细节 7文字对比度不达标浅灰色文字#CCCCCC在白色背景上的对比度只有 1.6:1远低于 WCAG AA 要求的 4.5:1。这影响的不仅是视障用户——在阳光直射下正常视力用户也看不清。使用 DevTools 的 Accessibility 面板或 WebAIM 的 Contrast Checker 检查每个颜色组合。细节 8动态内容更新不通知屏幕阅读器搜索建议列表异步加载完成后屏幕阅读器用户不知道有新的内容出现。Toast 通知弹出后也没有被朗读。正确做法动态内容容器添加aria-live。div rolestatus aria-livepolite aria-atomictrue !-- 搜索建议列表 -- /divaria-livepolite等待当前朗读完成后才播报aria-liveassertive立即打断当前朗读。三、生产级代码无障碍检查 CI 集成/** * 无障碍自动化检查脚本CI 集成 * * 使用 axe-core 自动化扫描无障碍问题 * 在 CI 中作为质量门的一部分 */ // CI 配置文件axe-check.ci.ts async function runAccessibilityCheck(url: string): Promise{ violations: any[]; score: number; } { const { AxePuppeteer } require(axe-core/puppeteer); const puppeteer require(puppeteer); const browser await puppeteer.launch(); const page await browser.newPage(); await page.goto(url, { waitUntil: networkidle0 }); // 执行 axe 扫描 const results await new AxePuppeteer(page) .withTags([wcag2a, wcag2aa, wcag21a, wcag21aa]) .analyze(); await browser.close(); const criticalOrSerious results.violations.filter( (v: any) v.impact critical || v.impact serious ); // 评分每个严重违规扣 10 分关键违规扣 20 分 const score Math.max( 0, 100 - criticalOrSerious.reduce( (sum: number, v: any) sum (v.impact critical ? 20 : 10), 0 ) ); return { violations: criticalOrSerious, score }; } // 阈值score 80 时 CI 失败 const RESULT await runAccessibilityCheck(http://localhost:3000); if (RESULT.score 80) { console.error(无障碍检查未通过); console.error(违规项:, JSON.stringify(RESULT.violations, null, 2)); process.exit(1); }四、边界分析自动化检测的覆盖率axe-core 能检测约 30% 的 WCAG 标准。焦点管理、键盘操作流畅性、屏幕阅读器朗读质量——这些必须手动测试。自动化检查是兜底不能替代手动审计。无障碍与设计美学的冲突高对比度模式下的界面通常不够好看。这是设计中必须接受的取舍——可访问性优先于美学。可以在标准模式下保留设计感在高对比度/辅助模式下简化视觉效果。团队的无障碍意识缺口自动化工具能发现问题但修复问题需要团队的无障碍意识。建议每个 Sprint 用 30 分钟做一次键盘-only 演示让开发者亲身体验他们构建的界面在纯键盘操作下的感受。五、总结焦点管理是可交互界面无障碍的基础焦点陷阱、焦点丢失、跳过链接是三个核心场景div 模拟按钮需要无遗漏地补齐 role、tabindex、键盘事件三要素纯图标按钮必须使用 aria-label 提供文本标签否则屏幕阅读器无法理解状态信息应当颜色 图标 文字三重编码不能仅依赖颜色文字对比度 4.5:1AA是最低标准推荐 7:1AAA动态内容区域必须添加 aria-live确保屏幕阅读器能感知变化axe-core 自动化检测覆盖 30% 标准手动审计仍然是必需的团队级无障碍意识比工具更重要——键盘-only 演示是最有效的培训