应监听表单submit事件,用querySelectorAll检查同name radio是否选中,未选中时对任一radio调用setCustomValidity并手动插入DOM提示元素,同时确保fieldset+legend语义结构完整且校验后清空状态。

单选按钮组没有选中时如何触发自定义校验提示
浏览器原生的 required 属性对 <input type="radio"> 组只在提交时校验,且错误提示固定在第一个 radio 元素上(即使它被隐藏),无法控制提示位置。必须手动拦截表单提交、检查是否有选中项,并主动调用 setCustomValidity()。
关键点在于:校验逻辑不能只绑在单个 radio 上,而要作用于整个 name 组。常见错误是给每个 radio 单独加 required,这会导致多个冗余校验和混乱的焦点跳转。
- 监听表单的
submit事件,而非单个radio的change - 用
document.querySelectorAll('input[name="xxx"]:checked')判断是否至少有一个被选中 - 若未选中,对任意一个同名
radio调用.setCustomValidity("请选择一项"),再调用.reportValidity() - 校验通过后,务必对所有同名
radio调用.setCustomValidity("")清空状态,否则后续提交会持续失败
把错误提示显示在自定义位置(比如 label 旁边)
原生 title 或浏览器默认气泡提示无法重定位。要实现“提示显示在 label 右侧”,得放弃 reportValidity() 的原生弹窗,改用 DOM 操作手动插入提示元素。
典型做法是:预先在 HTML 中放一个 <span class="error-message"></span> 紧跟在 radio 组的 <label> 后或容器底部,然后 JS 控制它的 textContent 和显隐。
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
立即学习“前端免费学习笔记(深入)”;
- 不要依赖
:invalid伪类自动显示提示——它只对单个输入生效,且无法精确定位到组级语义 - 提示元素建议用
aria-live="polite"保证屏幕阅读器可读 - 清空错误时,除了清空文本,还要移除
aria-invalid="true"和相关 CSS 类(如error) - 如果 radio 组由多个
<div>包裹,确保提示元素父容器有position: relative,方便绝对定位
避免 focus 管理错乱导致用户无法继续操作
手动校验后若直接 input.focus(),焦点会落在某个 radio 上,但用户可能想先看提示再选——更合理的是把焦点设在第一个 radio 的 label 上,或保持当前焦点,仅高亮提示区域。
- 不要在
submit处理函数里无条件调用.focus(),尤其当用户刚点击提交按钮时,焦点跳转会打断操作流 - 若必须引导焦点,优先聚焦到包含全部选项的容器(如
<fieldset>),并设置tabindex="-1"+.focus() - 使用
scrollIntoView({ block: 'nearest' })滚动到提示区域,比强制聚焦更友好 - 移动端要注意:iOS Safari 对
radio的focus()支持不稳定,label 聚焦更可靠
兼容性与无障碍要点
IE11 不支持 setCustomValidity() 对 radio 组的有效控制,且 reportValidity() 行为不一致。无障碍方面,radio 组必须有明确的 <legend>(在 <fieldset> 内),不能只靠视觉 label。
- 必用
<fieldset><legend>问题描述</legend>...</fieldset>结构,否则屏幕阅读器无法将选项归为一组 - 每个
radio的id必须唯一,对应label[for],不可省略 - 禁用 JS 时,应保留原生
required作为降级方案,但需接受其提示位置不可控 - Chrome 115+ 开始对未选中
radio组的reportValidity()提示做了微调,会高亮整个fieldset边框,但仍无法替代手动提示
fieldset + legend 的语义完整性。没这两项,要么提示反复触发,要么残障用户根本不知道这是一组互斥选项。


















