仅写 <nav> 或 <main> 标签不等于可访问,必须满足语义位置、内部可聚焦性、唯一性与列表结构四点硬约束,否则屏幕阅读器会跳过或误读。

直接结论:仅写 <nav> 或 <main> 标签不等于可访问;必须满足语义位置、内部可聚焦性、唯一性与列表结构四点硬约束,否则屏幕阅读器会跳过或误读。
为什么写了 <nav> 屏幕阅读器还是跳不过去
根本不是样式或 JS 干扰导致的,而是 <nav> 被嵌套在语义“断层”容器里——比如包在 <table>、<ul>、或带 role="group" 的 <div> 中,浏览器会直接丢弃该节点的地标语义。更常见的是 <nav> 内部只有纯文本链接,没有可聚焦元素(如 <a href>、<button>),或所有链接都缺 href,读屏器判定为“不可交互”,直接跳过。
- 确保
<nav>是<body>的直系子元素,或至少不被<table>、<ol>、<section role="group">等语义受限标签包裹 - 内部至少有一个真实可聚焦元素:
<a href="/home">、<button>,或带tabindex="0"的容器 - 多个
<nav>必须配aria-label,值要具体,例如aria-label="主导航",不能是"Navigation"
<main> 嵌套在组件壳里就等于没写
<main> 的唯一性是硬性约束,不是建议。一旦它被 React 的 <Layout>、Next.js 的服务端 wrapper、或 <header>/<section> 包裹,辅助技术就找不到核心内容锚点。Lighthouse 报 “Multiple main landmarks” 是直接失败,不是警告。
- 正确结构只能是:
<body><header></header><main></main><footer></footer></body>(三者并列) -
<main>内部不需要再套一层<section>——除非该<section>有独立标题且需参与大纲生成 - 框架强制输出嵌套 DOM 时(如 Next.js 默认把
<main>包在<div>里),必须用 CSSdisplay: contents或运行时 JS 移动节点,确保最终渲染的<main>是<body>的直系子节点
导航项不用 <ul> 就等于没结构
用 <div> 堆链接,哪怕加了 role="list",也会丢失键盘方向键导航、读屏器报数(“第1项,第2项”)等原生行为。屏幕阅读器默认把 <ul> 识别为“列表”,把所有链接连读成一串无结构的文本,用户根本无法分辨项数或顺序。
使用 PptxGenJS 和 Python 解析,将结构化的 HTML/CSS 幻灯片内容转换为原生、可编辑的 PowerPoint(.pptx)演示文稿。
立即学习“前端免费学习笔记(深入)”;
- 主导航、页脚链接等无强顺序关系 → 用
<ul> - 面包屑、步骤条、章节锚点等有明确层级或顺序 → 用
<ol>,读屏器会播报序号 - 当前页链接必须保留
href(哪怕指向当前 URL),并加aria-current="page";禁用项用disabled或aria-disabled="true"+tabindex="-1",不能删href
验证地标是否真实生效,别只看 DevTools 里的标签名
DevTools 里看到 <nav> 和 <main>,不代表它们被辅助技术识别为地标。真正起作用的是浏览器暴露给读屏器的 ARIA role、name 和 level。很多团队卡在“写了但无效”的阶段,就是因为只检查 HTML 源码,没走真实验证流。
- 用 NVDA + Firefox 或 VoiceOver + Safari,按
D(landmarks list)键打开地标列表,确认能否看到“主导航”“主内容”等命名项 - 用 Chrome DevTools 的 Accessibility 面板,展开节点,查看 Computed Properties 下的
role和name是否符合预期 - JS 动态更新导航状态(如下拉展开、当前页切换)时,必须同步操作
aria-expanded和aria-current,不能只改class
最常被忽略的其实是 <main> 的直系子元素约束和 <nav> 内部可聚焦性——这两点没有商量余地,错一个,整个导航路标就塌了。


















