旧项目可访问性优化应先查三类硬伤:div伪装语义标签、标题树断裂、空交互元素;修完这三处,80%键盘与屏幕阅读器用户即可正常使用。

旧项目做可访问性优化,别从WCAG条款开始读——先打开浏览器开发者工具的“Accessibility”面板,跑一次Lighthouse A11y审计,盯住三类硬伤:div伪装语义标签、标题树断裂、空交互元素。这三处修完,80%的键盘与屏幕阅读器用户就能正常使用。
怎么快速定位 div 伪装语义结构
很多团队以为加 role="navigation" 就算改完了,其实浏览器根本不认。真正要动的是 DOM 结构本身。
- 在控制台执行
document.querySelectorAll("div[role], div[aria-*]"),如果返回结果 > 20 个,说明大量用div打补丁,必须重构 - 搜索项目中所有
<div class="header">、<div class="nav">、<div class="main-content">,逐个替换成<header>、<nav aria-label="主导航">、<main> -
<main>必须是<body>的直接子元素,且全局只能出现一次;嵌套在<div id="app">里或服务端 include 多次,都会被 axe 标为 critical
标题层级断裂怎么修才不翻车
不是加 aria-level 或手动设 h3 就行——屏幕阅读器靠 DOM 层级跳转,断层等于逻辑塌方。
- 检查
h1是否缺失或重复:document.querySelectorAll("h1")应该只返回 1 个;多个h1会让 VoiceOver 直接忽略后续标题导航 - 用 Lighthouse 查 “Heading levels should only increase by one” 报错,重点看
h2后是不是突然跳到h4 - 常见诱因是 CSS
display: none隐藏了某个h3,但 DOM 还在;修复方式是删节点,或加aria-hidden="true"+ 视觉隐藏类,不能只藏不删 - 每个
<section>必须以h2–h6开头,否则它只是个普通容器,不是语义分组
表单和按钮为什么总被键盘用户卡住
能 Tab 进去,按 Enter 没反应,比完全不可聚焦更糟——这是焦点流断裂,不是缺 tabindex="0"。
立即学习“前端免费学习笔记(深入)”;
- 把所有
<div onclick="xxx()">和<span onclick="xxx()">替换为<button type="button">;别用role="button"伪装,它不触发默认表单行为,也不支持空格键 - 表单控件必须绑定
<label>:<label for="email">邮箱</label><input id="email" name="email" type="email">;禁用空<label for="xxx"></label>,AT 会静默跳过 - 单选/复选成组必须用
<fieldset><legend>偏好设置</legend>...</fieldset>;仅靠 CSS 分组,VoiceOver 在“组模式”下根本不知道这堆控件是一组 - 模态框打开时,焦点必须移到第一个可聚焦元素(如确认按钮);关闭后,焦点必须回到触发它的按钮上——用
document.activeElement和.focus()手动管理
为什么加了 aria-live 反而让屏幕阅读器更混乱
aria-live 不是“让 AT 知道有更新”的万能开关,它是广播机制,滥用等于持续插话。
- 禁止包裹整个
<div id="content">;应只包裹真正需要播报的动态区域,比如表单提交后的错误提示<div aria-live="polite" aria-atomic="true"></div> -
aria-live="polite"适合非中断性提示;aria-live="assertive"会强制打断当前朗读,只用于严重错误(如登录失败) - 确保
aria-labelledby或aria-describedby指向的 ID 真实存在,否则 AT 直接静默跳过该元素 - 给静态文字加
aria-hidden="true"是典型误用——视觉用户能看到,AT 用户却听不到,信息双丢失
真正难的不是写对某一行 ARIA,而是判断哪几个 div 必须立刻换成 <header>、<nav>、<main>,并确保它们的嵌套顺序符合原生语义契约。一旦结构失准,后面所有属性注入都是无效劳动。



















