语义化标签不可替代div,必须用等定义结构;跳过链接须为首个可聚焦元素并指向;ARIA是补丁而非替代,禁用冗余role;动态内容需手动管理焦点。

语义化标签必须替代 div 做结构划分
HTML5 的 <header></header>、<nav></nav>、<main></main>、<article></article>、<section></section>、<aside></aside>、<footer></footer> 不是“可选装饰”,而是屏幕阅读器识别页面逻辑结构的唯一依据。用 <div> 套一堆 class 模拟导航或主内容,等于主动屏蔽辅助技术。<p>实操建议:</p>
<ul>
<li>每个页面有且仅有一个 <code><main></main>,且不能嵌套在 <article></article> 或 <section></section> 内
<nav></nav> 应包含有意义的导航链接,纯装饰性图标栏别硬套 <nav></nav>
<section></section> 必须带 aria-labelledby 或 <h2>–<h6></h6>
</h2> 作为标题,否则会被读作“无名区域”<header></header> 套整个页眉+logo+搜索框+用户菜单——它只应包裹与当前 <article></article> 或页面相关的头部内容;全局页眉建议用 <header role="banner"></header>
跳过链接(skip link)不是可选项
键盘用户无法用鼠标点“返回顶部”,也无法靠视觉扫出“主内容从哪开始”。没有 <a href="#main">跳至主内容</a>,等于强制所有人从 logo、主导航、横幅广告逐个 tab 过去。
关键细节:
立即学习“前端免费学习笔记(深入)”;
- 跳过链接必须是页面第一个可聚焦元素(DOM 顺序第一),且默认隐藏(
position: absolute; left: -999px;),获得焦点时才显示 - 目标锚点必须是
<main id="main"></main>或其他语义化主容器,不能是<div id="main"> <li>若页面含多个 <code><main></main>(如 SPA 多视图),需动态更新跳过链接的href值 - 给已有语义的标签再加
role:如<nav role="navigation"></nav>——<nav></nav>本身已映射为 navigation role,冗余声明可能被某些旧读屏忽略或覆盖 -
aria-hidden="true"用在包含焦点元素的容器上(如弹窗遮罩层里塞了<button></button>),会导致键盘焦点进入“黑洞” -
aria-live区域未设polite/assertive,或频繁更新导致播报打断用户操作 - 用
aria-label覆盖可见文本时,没同步更新 DOM 文本(例如按钮文字变了但 aria-label 没改),造成信息不一致 - 模态框打开后,立即将焦点捕获到第一个可聚焦子元素(如
<input>或确认按钮),并用inert或aria-hidden="true"+tabindex="-1"锁定背景内容 - 关闭模态框后,焦点必须回到触发它的按钮(需提前缓存
document.activeElement) - Tab 切换时,新激活面板需包含至少一个可聚焦元素,且该元素应获得焦点(而非只靠 CSS
:focus-within) - 使用
scrollIntoView({ block: 'nearest', focus: true })替代简单focus(),避免焦点落在视口外
ARIA 属性不能乱加,尤其 role 和 aria-* 的组合
很多开发者看到“无障碍”就堆 role="region"、aria-label、aria-hidden="true",结果反而干扰阅读顺序或重复播报。ARIA 是补丁,不是语义替代品。
高频错误:
焦点管理在动态内容中极易失效
单页应用、模态框、手风琴菜单、Tab 切换组件——这些地方一旦 JS 插入/移除 DOM,浏览器不会自动重置焦点位置。用户按 Tab 键可能直接跳到地址栏,或卡在不可见元素上。
必须手动干预的场景:
最常被忽略的是:当用 display: none 隐藏内容时,它仍保留在焦点流中;应改用 hidden 属性或 aria-hidden="true" + tabindex="-1" 组合清理。


















