required 和 aria-required="true" 必须同时写在可聚焦的 input 元素上,二者缺一不可:required 负责原生校验与交互,aria-required="true" 为辅助技术提供语义;label 必须显式包含“(必填)”或通过 aria-label 补充,视觉星号需为真实 DOM 节点并配合 aria-hidden。

required 和 aria-required="true" 必须同时写在 input 上
只写 required,屏幕阅读器可能不播报“必填”;只写 aria-required="true",浏览器完全不校验、不拦截提交。二者必须共存,且都落在最终可聚焦的输入元素上(input、textarea、select),不能挂在 label 或 div 上。
常见错误现象:aria-required="true" 单独存在时,NVDA/VoiceOver 可能读出“必填”,但用户空着点提交,页面直接跳转,无任何提示。
-
required触发原生校验、影响移动端键盘类型(如type="email"唤出 @ 键)、支持:requiredCSS 伪类 -
aria-required="true"是给辅助技术的语义补充,现代读屏基本能识别required,加它主要是为旧版 JAWS/IE 或动态插入控件兜底 - React/Vue 自定义组件里极易漏传:检查 DevTools → 找到真实渲染的
input标签 → 确认两个属性都在
label 文本必须显式包含“必填”或用 aria-label 补充
required 属性本身不会让屏幕阅读器播报“必填”二字——它只管校验,不管播报。如果 label 写的是 <label for="email">邮箱</label>,读屏就只读“邮箱”,用户根本不知道要填。
最稳妥做法是把“(必填)”直接写进 label 文本:<label for="email">邮箱(必填)</label>。若设计不允许改文案,可用 aria-label,但必须保留字段名:
立即学习“前端免费学习笔记(深入)”;
- ✅ 正确:
<label for="email">邮箱</label><input id="email" required aria-required="true" aria-label="邮箱,必填"> - ❌ 错误:
aria-label="必填"(覆盖原名,读屏只说“必填”,不知是哪个字段) - ❌ 错误:
title或placeholder替代 label 提示(不稳定读出,且无法触发点击聚焦)
视觉星号 * 必须是 DOM 文本节点或配 aria-hidden + visually-hidden
CSS 伪元素 ::after 生成的星号不在 DOM 中,屏幕阅读器完全感知不到。仅靠 label:required::after { content: " *"; } 是无效的。
真正落地的写法只有两种:
- 把星号写成真实文本:
<label for="name">姓名*</label>,再用<span aria-hidden="true">*</span>包裹星号避免重复播报(如果 label 已含“(必填)”,星号纯属视觉冗余) - 用
visually-hidden类提供冗余文本:<label for="name">姓名<span class="visually-hidden">,必填</span></label>,配合 CSS.visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } - 高对比度模式下,别只靠红色——星号需有足够尺寸、固定位置(中文习惯后置),且不能仅靠颜色区分
动态表单和自定义控件里,aria-required 容易被忽略
JS 动态插入的表单项、React/Vue 封装的 MyInput 组件、用 div contenteditable 模拟的输入框,是最容易漏掉 aria-required="true" 的地方。一旦漏掉,残障用户根本无法获知该字段逻辑上必填。
关键检查点:
- 是否同步设置了
role?比如div加aria-required="true"但没设role="textbox",读屏直接忽略 - 是否在 JS 校验失败后,主动设置
aria-invalid="true"并关联错误消息(用aria-describedby)?否则用户提交空值后,读屏仍认为字段“正常” - 是否禁用了
aria-hidden="true"在父容器上?比如给整个表单外层div加了该属性,结果把里面所有输入框一并屏蔽
复杂点在于:所有 aria- 属性一旦出现,就必须由 JS 全程维护状态。比如切换必填开关时,不仅要改 required,还要同步 toggle aria-required 和 aria-invalid —— 漏掉任一环节,可访问性就断链了。



















