应使用CSS伪元素或JS安全操作添加视觉星号,保持label语义纯净;避免HTML内联星号干扰屏幕阅读器,required属性已隐含aria-required语义。

label 里直接拼接星号容易破坏可访问性
很多开发者习惯在 <label> 文本末尾手动加 *,比如 <label>用户名*</label>。这看似简单,但会把星号变成可聚焦、可朗读的内容,屏幕阅读器可能误读为“用户名星号”,干扰语义。真正合规的做法是用 CSS 控制视觉呈现,保持 HTML 语义纯净。
推荐做法:
- HTML 中 label 文本保持干净,不带星号:
<label for="name">用户名</label> - 用属性选择器匹配必填字段,例如给 input 加
required或自定义data-required - 通过
::after伪元素添加星号,仅作用于视觉层
CSS 伪元素 + 属性选择器精准控制星号位置
最稳妥的方式是利用 input[required] 或 input[data-required] 与相邻的 label 关联,再用 label[for="xxx"]::after 插入星号。但注意:CSS 无法跨元素向上选择父级或前驱,所以得靠结构配合。
推荐 HTML 结构(显式关联):
立即学习“前端免费学习笔记(深入)”;
<label for="email">邮箱地址</label> <input type="email" id="email" required>
CSS 实现:
label[for="email"]:has(+ input[required])::after {
content: " *";
color: #d32f2f;
font-size: 0.875em;
}
⚠️ 注意::has() 在 Safari 15.4+ 和 Chrome 105+ 支持良好,但旧版浏览器不支持。如需兼容 IE/旧 Edge,改用 class 驱动:
- 给必填
<label>加class="required" - 写
label.required::after { content: " *"; } - 同时确保该 class 不影响辅助技术(它只是样式钩子)
JavaScript 动态注入星号要避开 innerHTML 风险
如果表单字段是动态生成的,且无法提前加 class 或 rely on required 属性,可用 JS 注入星号。但绝不能用 label.innerHTML += " *" —— 这会销毁原有事件监听、重置 focus 状态,还可能触发 XSS(若 label 文本来自用户输入)。
安全做法是操作文本节点:
const label = document.querySelector('label[for="phone"]');
if (label && !label.hasAttribute('data-star-added')) {
const star = document.createTextNode(' *');
label.appendChild(star);
label.setAttribute('data-star-added', '');
}
更健壮的封装建议:
- 检查 label 最后一个子节点是否已是文本节点且以
*结尾,避免重复添加 - 用
textContent提取原始文本,再用document.createTextNode()安全追加 - 不依赖
innerHTML,规避解析 HTML 字符串的风险
aria-required 不等于视觉星号,别混用
aria-required="true" 是给辅助技术传达必填语义的,它本身不会渲染任何内容。很多人误以为加了这个属性就“自动有星号”,结果页面上干干净净,用户看不到提示。它和视觉星号是两件事:前者是无障碍声明,后者是 UI 暗示。
正确组合方式:
- 保留原生
required属性(触发表单校验、提供默认提示) - 用 CSS 或 JS 添加视觉星号(仅用于 sighted users)
-
aria-required一般不需要额外加 —— 原生required已隐含该语义 - 若用了自定义控件(如
div[role="textbox"]),才需手动补aria-required
星号颜色、大小、间距这些细节看着小,但一旦和表单验证状态联动(比如提交失败后高亮所有必填未填项),样式逻辑就容易错乱。最好把星号样式抽成独立 class,和错误态 class 解耦。



















