原生 required 属性对同 name 的 radio 组有效,校验是否至少选中一个;name 不一致、fieldset disabled、JS 动态修改或旧 WebView 兼容性差会导致校验失效。

单选框必须选中一个才能提交
原生 required 属性对 <input type="radio"> 有效,但前提是同一组 radio 必须共享相同的 name,且至少有一个选项存在(哪怕没设 checked)。浏览器只校验“该组是否有任意一个被选中”,不关心具体值是否为空字符串。
- 必须为所有同组 radio 设置完全一致的
name值,例如name="gender";不同 name 视为不同组,各自独立校验 - 无需给任一 radio 加
checked—— 缺省状态就是“未选中”,此时提交会触发校验失败 - 即使所有 radio 的
value都是空字符串(value=""),只要有一个被点选,校验即通过 - 不要用
disabled禁用某个 radio,它会被跳过校验,但用户也无法选择,容易造成逻辑断裂
为什么点了单选框还提示“请填写此字段”
常见原因不是 radio 本身问题,而是 HTML 结构或属性干扰了浏览器识别:
-
name值拼写不一致(比如大小写混用、多空格、前后缀差异),导致浏览器认为它们不属于同一组 - radio 被包裹在
<fieldset disabled>中,整个字段集被禁用,表单控件不参与验证和提交 - 使用了 JavaScript 动态移除/重写 radio 元素,但未同步更新
name或未触发浏览器重新解析表单约束 - 部分旧版 Android WebView 对
required+radio支持不全,首次点击后不弹提示,需手动调用checkValidity()
自定义错误提示文案要小心 setCustomValidity 的残留
radio 不像 <select> 或 <input> 那样有明确的“输入事件流”,所以不能依赖 oninput 清空错误。正确做法是监听 change 事件:
<form id="myForm">
<label><input type="radio" name="plan" value="basic" required> 基础版</label>
<label><input type="radio" name="plan" value="pro"> 专业版</label>
<button type="submit">提交</button>
</form>
<script>
const radios = document.querySelectorAll('input[name="plan"]');
radios.forEach(radio => {
radio.addEventListener('change', () => {
// 只清空当前组的自定义错误(如果有)
const form = radio.form;
if (form) form.checkValidity(); // 强制刷新验证状态
});
});
document.getElementById('myForm').addEventListener('submit', (e) => {
if (!e.target.checkValidity()) {
e.preventDefault();
// 可在此处统一处理错误 UI,比如聚焦第一个未选 radio
radios[0].focus();
}
});
</script>
注意:不要在每个 radio 上单独调用 setCustomValidity(),因为浏览器只认整个 name 组的验证状态;真正需要干预的是表单级的提交拦截与反馈逻辑。
立即学习“前端免费学习笔记(深入)”;
移动端 Safari 和 WebView 的兼容性坑
iOS 15+ 和较新 Android WebView 基本支持 required + radio,但仍有两个现实问题:
- 原生错误提示可能不显示,或只闪一下,用户感知弱 —— 建议配合 CSS
:invalid伪类加红边框,或用 JS 主动插入提示文案 - 如果页面用
formnovalidate提交过一次,后续再启用验证时,某些 WebView 不会自动恢复校验行为,需手动重置表单或重建 radio 节点
最稳妥的做法是:把 required 当作基础兜底,关键业务逻辑仍以 JS checkValidity() + 显式提示为主,别完全依赖浏览器弹窗。



















