语义化标签替代div堆砌是无障碍设计起点,如用而非,避免冗余aria补救;须包裹真实导航链接,每页唯一且不嵌套;组件模板应预留data-aria-*占位而非硬编码aria属性。

用语义化标签替代 div 堆砌,避免 aria-* 重复补救
可访问性不是靠后期加 aria-label 或 role="region" 强行弥补的,而是从结构起点就该有明确含义。比如页脚用 <footer></footer>,屏幕阅读器自动识别为“页脚区域”,无需额外声明;而写成 <div class="footer"> 后再加 <code>role="contentinfo",既冗余又容易漏掉 aria-labelledby 等配套属性。
常见错误现象:多个页面共用同一套 JS 注入导航栏,但注入容器是 <div id="nav">,导致无障碍树中缺失导航语义,NVDA/JAWS 无法通过快捷键跳转到导航区。<ul>
<li>
<code><nav></nav> 必须包裹真正起导航作用的链接组,不能只是视觉上像导航
<main></main> 每页只能有一个,且不应嵌套在 <article></article> 或 <section></section> 内部<article></article> 而非 <div class="card">,它天然支持 <code>aria-live 和焦点管理<label for="id"></label>,不能只靠视觉对齐或 aria-labelledby 替代模板中预留可配置的 aria 属性锚点,而非硬编码
用 <template></template> 封装布局组件时,别把 aria-label、aria-describedby 这类属性写死。它们往往随上下文变化——同一套页脚模板,在不同站点需对应不同的备案号 ID、不同语言的版权文案。
实操建议:在模板里用 data-aria- 占位,由实例化函数注入真实值。例如:
<template id="footer-template"><footer data-aria-label="copyright" data-aria-describedby="备案信息"><p data-bind="copyright"></p>
<p data-bind="icp"></p>
</footer></template>
工厂函数 createFooter({ copyright: '© 2026', icp: '京ICP备12345678号' }) 在克隆后,会遍历节点查找 data-aria-* 并设置对应属性,同时清除 data- 前缀。
- 禁止在模板内直接写
aria-label="Copyright © 2026"—— 无法本地化,也无法被 JS 动态覆盖 -
aria-hidden="true"只用于纯装饰性元素(如图标字体),不能用于隐藏关键内容 - 多语言站点中,
lang属性应由外层 HTML 控制,组件模板里不设默认值
slot 分发时保留原生语义链,不破坏焦点顺序
Web Components 用 <slot></slot> 分发内容时,容易忽略插入内容本身的可访问性上下文。比如一个 <my-card></my-card> 组件,若把 <h2>标题</h2> 插入匿名 slot,它在 Shadow DOM 外仍保持 <h2></h2> 语义,但若组件内部用了 display: contents 或 position: absolute,可能让标题脱离文档流,导致屏幕阅读器跳过。
关键判断点:是否需要 slot 内容参与页面标题层级、焦点管理、键盘导航顺序?如果需要,就不能用 shadowRoot.innerHTML 直接拼字符串,而得用 slot.assignedNodes() 显式处理。
- 带交互的 slot 内容(如按钮、输入框)必须保留在 light DOM 中,否则
tabindex和focus()失效 - 命名 slot(
<slot name="header"></slot>)比匿名 slot 更可控,能明确约束插入位置和语义边界 - 不要在
connectedCallback里重写 slot 内容的id或for属性——这会切断原有 label 关联 - 若组件自身需聚焦(如自定义
<my-modal></my-modal>),应在focus()前确保 slot 内容已就绪,可用slot.addEventListener('slotchange', ...)
构建期注入 vs 运行时 fetch:可访问性友好与否的分水岭
用 fetch() 加载 header.html 再 innerHTML 插入,看似模块化,实则破坏可访问性:DOM 尚未解析完成时,屏幕阅读器已开始构建无障碍树;异步插入的内容不会触发 aria-live 区域更新,也不会被搜索引擎索引。
真正可复用的通用组件,必须在 HTML 初始加载时就存在完整语义结构。这意味着模块拆分必须发生在构建阶段,而非运行时。
- 推荐工具:
posthtml-include,语法是<include src="partials/footer.html"></include>,输出的是静态 HTML - 禁止在
<template></template>里写<script></script>标签——JS 行为应由主页面统一控制,避免多个 footer 实例各自执行初始化逻辑 - 所有 partial 文件中的
<img>必须含alt,且不能为占位符如alt=""(除非纯装饰) - 构建配置中确保 partial 路径相对于入口 HTML,否则
include失败会导致语义结构缺失,而非样式错乱
复杂点在于:可访问性不是开关式的“开了就行”,它依赖结构、属性、行为三者同步生效。一个 <nav></nav> 标签写对了,但里面链接用 div onclick 实现,照样不可键盘操作;模板预留了 data-aria-,但实例化函数忘了赋值,照样被读屏软件跳过。真正复用起来,得把每个组件当成独立的无障碍单元来验证,而不是当成视觉块来拼装。











