必须避免硬编码动态 aria 属性(如 aria-expanded)、在原生交互元素上重复添加 role、同时使用 aria-label 和 aria-labelledby、用 aria-label 替代视觉 label、标题跳级、未校验的颜色对比度、纯 CSS 悬停变色无 focus 支持。

用 role 和 aria- 属性补语义缺口时,哪些情况必须避免硬编码
当原生 HTML 标签无法准确表达组件行为(比如自定义下拉菜单、手风琴面板),role 和 aria- 是必要补充。但硬编码 aria-expanded="false" 或 aria-hidden="true" 会立刻失效——状态没随 JS 逻辑更新,屏幕阅读器就听到“假话”。
- 所有动态属性(
aria-expanded、aria-selected、aria-checked)必须由 JS 实时同步,不能写死在 HTML 中 -
role="button"只能加在非交互原生元素上(如<div>),若已用<button>就别再加role,否则可能覆盖默认语义 -
aria-label和aria-labelledby二选一即可,同时存在时后者优先,容易引发冗余朗读
表单控件的标签绑定,为什么 for/id 匹配比 aria-labelledby 更可靠
视觉上把 <label> 放在输入框旁边、不写 for 属性,或只靠 aria-labelledby 关联,都会让部分屏幕阅读器漏读或错读。W3C 明确要求:显式 for 与 id 匹配是表单可访问性的基线。
-
<label for="email">邮箱</label><input id="email" type="email">是最简、最稳的写法 - 若标签内容复杂(含图标、多段文本),可用
aria-labelledby="id1 id2",但前提是这些 ID 确实存在且内容无歧义 - 绝对不要用
aria-label替代视觉标签——它会完全屏蔽可见文字,对低视力用户反而更难定位
标题层级断裂会导致整个文档大纲错乱,怎么快速验证
跳级(比如从 <h2> 直接到 <h4>)会让屏幕阅读器用户失去上下文,也破坏 SEO 结构。这不是“看起来没问题”就能放过的细节。
- 浏览器地址栏输入
javascript:document.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach((h, i) => console.log(h.tagName, h.textContent.trim().slice(0,30)));void 0,直接看控制台输出的层级序列 - 用 axe 浏览器插件跑一次全页扫描,
heading-order规则会标出所有断裂点 - 组件库中复用的
<h3>标题,如果嵌入到父级为<h2>的卡片里,就得动态降级成<h4>,不能写死
颜色对比度不足不是设计问题,而是可访问性合规红线
WCAG 2.1 AA 级要求正文文本与背景对比度 ≥ 4.5:1,大号文本(≥18pt 或加粗 ≥14pt)≥ 3:1。低于此值,色弱用户根本无法识别。
立即学习“前端免费学习笔记(深入)”;
- 别信“设计师说这个灰很好看”——用 WebAIM Contrast Checker 或 Chrome DevTools 的 Accessibility 面板实时测
- CSS 中避免用
color: #999这类未校验的灰阶,改用已通过对比度测试的变量,如--text-secondary: #666 - 禁用纯 CSS 控制的“悬停变色”,必须搭配
focus状态,否则键盘用户无法感知焦点位置
<button>、<select>、<dialog> 时,都记得检查它的键盘流、焦点管理、错误反馈和屏幕阅读器播报顺序——这些细节一旦松动,一致性就崩了。



















