必须且只能作为的直接子元素,否则会破坏可访问性、导致seo降权、触发lighthouse报错;嵌套或重复使用将使屏幕阅读器跳过或误读主体内容。

直接说结论:HTML无障碍体验提升,核心不是堆ARIA,而是回归语义化本身——<button></button>就别用<div>模拟,<code><main></main>必须是的直接子元素,lang属性漏写会导致NVDA/VoiceOver全程读英文音调。
为什么<main></main>必须是的直接子元素?
这不是规范“较真”,而是辅助技术解析DOM时的真实依赖。若<main></main>被包在<div class="wrapper">里,部分旧版NVDA会跳过它,VoiceOver在iOS上可能将其降级为普通<code><div>处理,导致“跳到主要内容”快捷键失效。
<ul><li>
<code><main></main>只能出现一次,且必须是的**直系子节点**(不能嵌套在<section></section>、<div>或任何其他非语义容器中)
<li>多页应用中,每次路由切换后需检查<code><main></main>是否仍满足该结构,单页渲染容易意外破坏
<div class="app">作为根容器,应直接将<code>class移到<main></main>上,而非包裹它
aria-label和aria-labelledby到底怎么选?
选错不只浪费代码,还会触发Lighthouse报错或让屏幕阅读器静音关键文本。根本判断标准就一条:描述文本是否已在页面中可见并稳定存在。
- 用
aria-labelledby:当标签文字已存在且有id,比如搜索框旁有<label id="search-label">站内搜索</label>,输入框写aria-labelledby="search-label"——文案改了、翻译切了,辅助技术自动同步 - 用
aria-label:纯图标按钮(<button aria-label="删除"><svg></svg></button>)或上下文极度明确的隐藏控件(如分页中的“下一页”箭头) - 绝对禁止:
<button aria-label="提交">发送</button>——“发送”会被静音;也禁止同时写两个,aria-labelledby会被忽略
表单控件没<label></label>为什么比没样式还危险?
没有<label></label>的<input>,对屏幕阅读器用户来说等于“一个不可名状的空白输入框”。它不播报用途、不关联错误提示、验证失败时焦点跳转也失效。
- 必须二选一:
<label for="email">邮箱</label><input id="email">,或<label>邮箱<input></label>(嵌套写法更鲁棒) - 单选/复选成组时,必须用
<fieldset></fieldset>+<legend></legend>包裹,否则每个选项会被读作孤立项,失去“请选择性别”这类上下文 -
aria-required="true"不能替代视觉星号,它只通知辅助技术;两者需同步更新,否则造成语义与UI脱钩
aria-hidden="true"加在哪会直接“封印”整个区域?
它不是CSS display: none,而是向辅助技术下达“彻底无视此节点及所有后代”的指令,且子元素无法解除。
- 模态框打开时,**不要**给
加aria-hidden="true"——这会让整个页面对读屏器消失,包括模态框本身 - 正确做法:用
inert属性(现代浏览器支持),或手动tabindex="-1"+focus()锁定焦点在模态框内可操作元素上 - 仅可用于纯装饰性内容:比如加载中的
<div class="spinner">、无意义的分割线、背景图标(前提是旁边有带<code>alt的<img>或可见文本说明)最易被忽略的其实是
和<meta charset="UTF-8">这两行——它们不出现在DevTools的Accessibility面板里,但一旦缺失,所有后续优化都建立在错误语音引擎和乱码基础之上。











