必须配aria-label才能让屏幕阅读器区分多个区域,否则仅读作“导航区域”;单独写无效,因它只是语义容器而非用途说明书;等原生标签已隐式含role,加role冗余且触发警告。

HTML文档结构的无障碍,不靠往<div>上堆<code>aria-属性,而靠三件事:用对语义标签、只在必要处补ARIA、JS状态必须同步。绝大多数项目加了aria-label反而更糟,因为掩盖了结构缺陷。
为什么
单写<nav></nav>,屏幕阅读器只读“导航区域”,无法区分是主导航、面包屑还是页脚链接。当页面有多个<nav></nav>时,辅助技术完全无法上下文定位。<nav aria-label="Main navigation"></nav>是安全写法;<nav aria-label="Navigation"></nav>等同于没写。
<main></main>本身已隐式对应role="main",再加role="main"冗余,部分校验工具会报警告;且<main></main>在页面中只能出现一次,嵌套在<section></section>或<article></article>内会破坏语义层级。
- 英文界面统一用
aria-label="Main navigation",避免拼写变体(如"Breadcrumbs"和"Breadcrumb"混用) -
<header></header>、<footer></footer>同理:优先用原生标签,不加role;若用<div class="header">模拟,才需补<code>role="banner" - 别写
<main role="main"></main>——浏览器自动映射,手动覆盖无益反害 - 绝对不要同时写
aria-label和aria-labelledby——多数浏览器只读aria-label,后者被静音 -
<button aria-label="提交">发送</button>:屏幕阅读器只读“提交”,“发送”被丢弃 - 多语言站点慎用
aria-label硬编码,优先用aria-labelledby引用动态文案 - 给整个
加aria-hidden="true"来“隐藏背景”:模态框打开时,键盘焦点仍可进入不可见区域,读屏器却已跳过,用户卡死 - 正确做法是用
inert属性(现代浏览器支持),或用tabindex="-1"+focus()锁定焦点在模态框内 - 纯装饰性图标可加
aria-hidden="true",但前提是旁边已有可见文字(如“用户名”+图标);若图标承载信息(如带alt的logo),就不能关 - 自定义下拉需同时管理:
role="listbox"+aria-expanded="true"+aria-activedescendant="item-id" - 模态框必须四要素齐备:
role="dialog"+aria-modal="true"+aria-labelledby="title-id"+ 焦点锁定 -
<div onclick="submit()">提交</div>必须加role="button"+tabindex="0"+ 监听Enter/Space事件;更推荐直接用<button></button> - 用
innerHTML = newContent替换aria-live区域会导致监听丢失——必须用appendChild()或insertAdjacentElement()插入新节点
aria-label和aria-labelledby混用会静音可见文本
核心判断标准:描述文本是否已在页面可见?可见就复用,不可见才新建。
图标按钮(如<button>×</button>)用aria-label="关闭弹窗";表单字段旁已有<label id="email-label">邮箱地址</label>,就用<input aria-labelledby="email-label">。
aria-hidden="true"不是CSS隐藏开关,而是继承式屏蔽
aria-hidden="true"只对辅助技术生效,视觉用户照常看到,JS照常操作——这点被误用最多。
它强制忽略节点及其所有子节点,子元素设aria-hidden="false"也无效。常见翻车场景:
自定义组件光加role不够,必须配对状态+键盘事件
仅设role="listbox"或role="dialog"只是告诉读屏器“这是什么”,不等于它能用。语义和行为必须同步:
最易被忽略的点:JS更新DOM后,ARIA状态必须实时响应。比如菜单展开后,aria-expanded值变了,但DOM里没同步,读屏器就会说“已展开”,用户却看不到内容——语义和行为彻底脱钩。











