语义化标签是可访问性树构建的关键基础设施,而非装饰;<nav>不可被<div role="navigation">替代,因前者具隐式role、键盘管理及快捷键支持,后者缺乏这些能力且兼容性差。

语义化标签不是“让页面看起来更规范”的装饰,而是决定屏幕阅读器能否正确构建可访问性树的关键基础设施。不用 <nav> 而用 <div class="nav">,等于主动切断键盘用户和视障用户的导航入口。
为什么 <nav> 不能被 <div role="navigation"> 替代
两者在辅助技术中行为差异极大:<nav> 是隐式带 role="navigation" 的原生元素,浏览器和主流屏幕阅读器(如 NVDA、VoiceOver)会自动识别,并提供“跳转到导航区”快捷键(如 NVDA + Insert + N);而手动加 role 的 <div> 缺少内置键盘焦点管理、无默认 tabindex、不响应空格/回车激活,且部分老旧组合(如 IE11 + JAWS)可能完全忽略该 role。
常见错误包括:
-
<nav>内部只放纯文本,没包裹可聚焦元素(如<a>或带tabindex="0"的<button>) - 多个
<nav>未用aria-label区分,例如页脚导航也写成<nav>却没加aria-label="页脚导航" - 把
<nav>嵌套在<table>或<ul>里,违反 HTML5 内容模型,导致部分浏览器直接丢弃节点
<main> 的唯一性与 DOM 位置陷阱
<main> 必须且只能出现一次,它代表页面中用户真正想看的主体内容——不是视觉居中容器,也不是样式 wrapper。一旦缺失、重复或嵌套错误,屏幕阅读器就无法准确定位“主要内容起始点”,用户按快捷键(如 VoiceOver 的 Ctrl+Option+Cmd+Down)将直接失效。
立即学习“前端免费学习笔记(深入)”;
推荐结构是:<body> → <header> → <main> → <footer>,中间不插入任何无语义的 <div id="root"> 或 <div class="wrapper">。
动态渲染场景下特别注意:
- React/Vue 的根容器(如
<div id="app">)不应包裹<main>,否则破坏其语义层级 - SSR 注入的占位节点若提前闭合了
<main>,会导致后续内容脱离主区域 -
<main>内可嵌套<header>和<footer>,但不要用<section>替代<main>的主体地位
<label> 关联失败的三个隐藏原因
即使写了 <label for="email">邮箱</label><input id="email">,仍可能被屏幕阅读器跳过——问题常不在语法,而在执行环境。
高频踩坑点:
- ID 值含特殊字符(如
user-email@domain):HTML ID 不允许@、.、空格等,应改为user_email或userEmail - 动态生成的
<input>缺少id:React 中未显式设置id属性,或用Math.random()生成却未同步更新for值 -
<label>内容为空或仅含空格:某些 AT(尤其是移动端 VoiceOver)会直接跳过该<label>,哪怕已正确绑定
替代方案优先级:显式 for/id 绑定 > 隐式包裹(<label>用户名<input></label>) > aria-labelledby(仅当结构无法调整时)。
<section> 和 <article> 的语义边界在哪
<section> 表达“主题内容分组”,必须以 <h2>–<h6> 开头,否则语义退化为普通 <div>;<article> 表示“可独立分发或复用的内容”,如博客文章、新闻稿、论坛帖子——它自带隐式 heading 上下文,内部 <h1> 是合法且推荐的。
误用典型:
- 把整篇博客正文塞进
<section>:弱化了其“独立实体”价值,应改用<article> - 在
<section>里放<h1>:破坏标题层级连续性,屏幕阅读器导航时会“掉出章节” - 用
<section>替代<main>:前者是分组容器,后者是 Landmark 区域,功能不可互换
复杂页面(如仪表盘)建议结构:<main> → 多个 <section>(每组配 <h2>)→ 独立卡片用 <article>(如“今日统计”“异常告警”)。



















