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

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


















