原生语义化标签是构建无障碍组件库的唯一可靠路径;因其自动提供焦点管理、键盘交互(Enter/空格触发)、disabled同步及ARIA状态,而div+role需手动补全所有能力,极易遗漏关键环节。

直接用原生语义化标签构建组件库,是唯一能兼顾可维护性、键盘可用性和读屏器兼容性的路径;任何用 div + role 模拟按钮、导航或 tab 的做法,都会在焦点管理、状态同步或键盘交互上漏掉至少一个关键环节。
为什么 button 标签比 role="button" 更可靠
浏览器对 button 的行为有统一实现:自动获得 tabindex="0"、响应空格/回车、触发 click 事件、支持 disabled 属性并同步视觉样式。而手动给 div 加 role="button" 后,必须自行补全所有这些能力。
- 漏掉
tabindex="0"→ 键盘用户无法聚焦该元素 - 没监听
keydown中的"Enter"和" "(空格)→ 按键无响应 - 禁用时只改 CSS 灰度,不设
disabled属性 → 屏幕阅读器仍播报“可点击”,且表单提交时仍可能被包含 - 没同步
aria-pressed或aria-expanded→ 开关类按钮的状态对辅助技术不可见
nav 和 main 必须配 aria-label 才算真正可用
单写 <nav> 或 <main> 不足以让读屏器区分多个同类区域。NVDA 或 VoiceOver 遇到多个 nav 时,默认只报“导航区域”,然后跳过——用户根本不知道哪个是主导航、哪个是页脚快捷入口。
-
<nav aria-label="主导航">和<nav aria-label="面包屑导航">是必需的显式标识 -
aria-current="page"必须加在当前页的a元素上,值只能是"page",不是"true"或"active" -
<main>建议配合id和aria-labelledby指向页面主标题,避免多个main冲突
自定义组件里怎么透传语义,而不是覆盖它
用 Web Components 封装时,常见错误是在 Shadow DOM 里塞一堆 div,再靠 aria-label 补救。这等于放弃语义,又想拿无障碍成果。
立即学习“前端免费学习笔记(深入)”;
- 注册为
<product-card>时,内部模板应使用<slot name="title">和<slot name="content">,而非硬写<div role="article"> - 对外暴露的
slot名称必须带语义,比如slot="header"、slot="footer",不能叫slot="top" - 如果组件本质是按钮(如
<icon-button>),其根节点必须是button,而非div包裹后再加role
树形控件(tree)的 aria-hidden 和 aria-expanded 必须成对操作
仅靠 CSS 的 display: none 或 visibility: hidden 隐藏子节点,对屏幕阅读器完全无效。NVDA/JAWS 会照常朗读那些“视觉隐藏但 DOM 存在”的节点。
- 折叠时:设
aria-expanded="false",并在直接子容器(role="group")上加aria-hidden="true" - 展开时:移除子容器的
aria-hidden,设aria-expanded="true" - 每个
role="treeitem"必须包含一个可聚焦元素(button或带tabindex="0"的span),否则无法被键盘选中 - 键盘支持必须覆盖
"ArrowRight"、"ArrowLeft"、"Home"、"End"、"ArrowUp"、"ArrowDown"六个键,不能只处理Tab
真正的难点不在写多少 ARIA 属性,而在理解哪些行为是浏览器原生保障的、哪些必须手动补全——一旦混淆,就容易漏掉某个键盘路径或状态同步点,导致功能对部分用户彻底不可用。



















