radio 单选按钮
type="radio" 创建单选按钮,允许用户在一组互斥的选项中选择一个。同一 name 属性的单选按钮组成一个单选组(radio group),用户只能选择组内的一个选项。本节将详细讲解单选按钮的互斥机制、value 属性、checked 默认选中,以及实现 radio group 的最佳实践。
前置知识
阅读本节前,建议先了解:search 搜索输入
基础概念
什么是单选按钮
单选按钮(radio button)是一种互斥选择控件,呈现为小圆形按钮。用户点击时按钮被选中(填充圆点),同组中的其他按钮自动取消选中。
<!-- 一组性别单选按钮 -->
<label><input type="radio" name="gender" value="male"> 男</label>
<label><input type="radio" name="gender" value="female"> 女</label>
<label><input type="radio" name="gender" value="other"> 其他</label>
<!-- 三个按钮的 name 相同,组成一个互斥组 -->radio 的核心特征
| 特征 | 说明 |
|---|---|
| 选择方式 | 点击(或键盘空格/方向键) |
| 互斥性 | 同 name 的 radio 互斥 |
| 提交值 | 选中项的 value 属性值 |
| 默认选中 | checked 属性 |
| 取消选择 | 不可(一旦选中只能切换到其他选项) |
语法
基本语法
<input
type="radio"
name="groupName"
id="option1"
value="提交值"
checked
disabled
>
<label for="option1">显示文本</label>属性说明
| 属性 | 值 | 说明 |
|---|---|---|
type | "radio" | 指定为单选按钮 |
name | 字符串 | 组名,同名 radio 互斥 |
value | 字符串 | 选中时提交的值 |
checked | 布尔属性 | 默认选中 |
disabled | 布尔属性 | 禁用该选项 |
id | 字符串 | 唯一标识,用于 label 关联 |
required | 布尔属性 | radio group 中至少选择一项 |
详细说明
name 属性与互斥机制
name 属性是单选按钮的核心属性。浏览器通过 name 判断哪些单选按钮属于同一组,从而实现互斥选择。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>name 互斥机制</title>
</head>
<body>
<h2>选择你喜欢的编程语言</h2>
<form action="/vote" method="post">
<!--
name="language" 的所有 radio 组成一个互斥组
选择 JavaScript 后,HTML 和 CSS 自动取消选中
-->
<label>
<input type="radio" name="language" value="html"> HTML
</label>
<label>
<input type="radio" name="language" value="css"> CSS
</label>
<label>
<input type="radio" name="language" value="javascript"> JavaScript
</label>
<label>
<input type="radio" name="language" value="python"> Python
</label>
<!--
不同 name 的 radio 互不影响
这里 name="level" 是另一个独立的组
-->
<h3>你的水平</h3>
<label>
<input type="radio" name="level" value="beginner"> 初学者
</label>
<label>
<input type="radio" name="level" value="intermediate"> 中级
</label>
<label>
<input type="radio" name="level" value="advanced"> 高级
</label>
<button type="submit">提交</button>
</form>
<!--
提交的数据(假设选择 JavaScript 和 中级):
language=javascript&level=intermediate
-->
</body>
</html>name 必须相同
同一组内的所有单选按钮必须有相同的 name 属性值。如果 name 不同,它们不属于同一组,可以实现多选(但这违背了 radio 的设计目的,应该使用 checkbox)。
value 属性
value 属性决定了选中该选项后提交到服务器的值。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>value 属性</title>
</head>
<body>
<h2>value 的重要性</h2>
<form action="/order" method="post">
<!--
错误!缺少 value 属性
提交的值为 "on",无法区分选中的是哪个选项
-->
<h3>错误示例(缺少 value)</h3>
<label><input type="radio" name="size_bad"> 小号</label>
<label><input type="radio" name="size_bad"> 中号</label>
<label><input type="radio" name="size_bad"> 大号</label>
<!-- 无论选择哪个,提交的都是 size_bad=on -->
<!--
正确:每个 radio 都有有意义的 value
-->
<h3>正确示例(有 value)</h3>
<label><input type="radio" name="size" value="S"> 小号</label>
<label><input type="radio" name="size" value="M"> 中号</label>
<label><input type="radio" name="size" value="L"> 大号</label>
<!-- 选择中号提交:size=M -->
<button type="submit">提交</button>
</form>
</body>
</html>严重错误
每个 <input type="radio"> 都必须设置 value 属性。如果不设置,提交的值固定为 "on",服务器无法判断用户选择了哪个选项。
checked 默认选中
checked 属性设置单选按钮的默认选中状态。同一组内只应有一个 radio 设置 checked(如果有多个,浏览器会选择最后一个)。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>checked 默认选中</title>
</head>
<body>
<h2>配送方式</h2>
<form action="/order" method="post">
<!-- 普通快递(默认选中) -->
<label>
<input type="radio" name="shipping" value="standard" checked>
普通快递(免费,3~5天)
</label>
<br>
<!-- 加急快递 -->
<label>
<input type="radio" name="shipping" value="express">
加急快递(¥15,1~2天)
</label>
<br>
<!-- 当日达 -->
<label>
<input type="radio" name="shipping" value="sameday">
当日达(¥30,今天送达)
</label>
<button type="submit">提交</button>
</form>
</body>
</html>label 关联(两种方式)
为单选按钮关联 <label> 有两种方式,推荐第一种(更易维护):
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>label 关联方式</title>
<style>
label {
display: block;
margin: 8px 0;
padding: 8px 12px;
cursor: pointer;
border-radius: 6px;
transition: background-color 0.2s;
}
label:hover {
background-color: #f0f0f0;
}
</style>
</head>
<body>
<h2>label 关联单选按钮</h2>
<!--
方式一:label 包裹 input(推荐)
点击 label 内的任意位置都能选中 radio
-->
<h3>方式一:嵌套(推荐)</h3>
<label>
<input type="radio" name="method1" value="email">
邮箱通知
</label>
<label>
<input type="radio" name="method1" value="sms">
短信通知
</label>
<label>
<input type="radio" name="method1" value="push">
推送通知
</label>
<!--
方式二:label 的 for 属性关联 input 的 id
点击 label 文字时也能选中 radio
-->
<h3>方式二:for + id</h3>
<input type="radio" name="method2" value="email" id="m2email">
<label for="m2email">邮箱通知</label>
<br>
<input type="radio" name="method2" value="sms" id="m2sms">
<label for="m2sms">短信通知</label>
<br>
<input type="radio" name="method2" value="push" id="m2push">
<label for="m2push">推送通知</label>
</body>
</html>radio group 与 required
当一组 radio 中没有默认选中项时,用户可能不选择任何选项就提交表单。使用 required 属性可以强制用户必须选择一项。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>radio required</title>
</head>
<body>
<h2>用户满意度调查</h2>
<form action="/survey" method="post">
<!--
required 只需要设置在 radio group 的其中一个上
浏览器会检查整个组是否至少有一项被选中
-->
<p>请评价我们的服务:</p>
<label>
<input type="radio" name="rating" value="5" required> 非常满意
</label>
<label>
<input type="radio" name="rating" value="4"> 满意
</label>
<label>
<input type="radio" name="rating" value="3"> 一般
</label>
<label>
<input type="radio" name="rating" value="2"> 不满意
</label>
<label>
<input type="radio" name="rating" value="1"> 非常不满意
</label>
<!--
注意:required 不需要设置在每个 radio 上
只要在组的任意一个 radio 上设置即可
也可以设置在每个上(效果相同,但冗余)
-->
<button type="submit">提交</button>
</form>
</body>
</html>键盘导航
单选按钮支持键盘操作,对于无障碍访问非常重要:
| 按键 | 行为 |
|---|---|
Tab | 聚焦到 radio group 的选中项(或第一项) |
空格 | 选中当前聚焦的 radio |
上/左方向键 | 选中上一个选项 |
下/右方向键 | 选中下一个选项 |
Tab | 离开 radio group |
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>键盘导航</title>
</head>
<body>
<h2>使用键盘操作单选按钮</h2>
<p>操作方式:Tab 聚焦 -> 方向键切换 -> 空格选中</p>
<!--
使用 fieldset + legend 为 radio group 添加分组标题
这对屏幕阅读器非常重要
-->
<fieldset>
<legend>选择你的操作系统</legend>
<label><input type="radio" name="os" value="windows"> Windows</label><br>
<label><input type="radio" name="os" value="macos"> macOS</label><br>
<label><input type="radio" name="os" value="linux"> Linux</label><br>
<label><input type="radio" name="os" value="other"> 其他</label>
</fieldset>
</body>
</html>JavaScript 操作 radio
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>JS 操作 radio</title>
</head>
<body>
<form id="planForm">
<fieldset>
<legend>选择套餐</legend>
<label><input type="radio" name="plan" value="free"> 免费版</label><br>
<label><input type="radio" name="plan" value="pro" checked> 专业版</label><br>
<label><input type="radio" name="plan" value="enterprise"> 企业版</label>
</fieldset>
</form>
<div id="info" style="margin-top: 16px;"></div>
<script>
// ---- 获取当前选中的值 ----
// 方式一:querySelector + :checked
const selected = document.querySelector('input[name="plan"]:checked');
console.log(selected ? selected.value : '未选择');
// 方式二:遍历所有同 name 的 radio
const radios = document.querySelectorAll('input[name="plan"]');
let checkedValue = '';
radios.forEach(radio => {
if (radio.checked) {
checkedValue = radio.value;
}
});
console.log(checkedValue); // 输出:pro
// ---- 监听变化 ----
const infoDiv = document.getElementById('info');
radios.forEach(radio => {
radio.addEventListener('change', function() {
if (this.checked) {
infoDiv.textContent = '你选择了:' + this.value;
}
});
});
// ---- 通过 name 直接访问(旧方式) ----
const plan = document.planForm.plan;
// plan 是一个 RadioNodeList
console.log(plan.value); // 输出当前选中的 value
// ---- 通过 form.elements 访问 ----
const form = document.getElementById('planForm');
console.log(form.elements['plan'].value); // pro
// ---- 编程方式选中 ----
// document.querySelector('input[name="plan"][value="enterprise"]').checked = true;
</script>
</body>
</html>实战示例
自定义样式的单选按钮组
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>自定义 radio 样式</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
max-width: 500px;
margin: 40px auto;
padding: 20px;
}
h2 { margin-bottom: 20px; }
/* 隐藏原生 radio */
.radio-group input[type="radio"] {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.radio-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.radio-label {
display: flex;
align-items: center;
padding: 14px 16px;
border: 2px solid #e0e0e0;
border-radius: 10px;
cursor: pointer;
transition: all 0.2s;
}
.radio-label:hover {
border-color: #b0d0f0;
background-color: #f8fbff;
}
/* 自定义圆形指示器 */
.radio-indicator {
width: 20px;
height: 20px;
border: 2px solid #ccc;
border-radius: 50%;
margin-right: 12px;
position: relative;
flex-shrink: 0;
transition: border-color 0.2s;
}
.radio-indicator::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 10px;
height: 10px;
border-radius: 50%;
background-color: #4a90d9;
opacity: 0;
transition: opacity 0.2s;
}
/* 选中状态 */
.radio-group input[type="radio"]:checked + .radio-label {
border-color: #4a90d9;
background-color: #f0f7ff;
}
.radio-group input[type="radio"]:checked + .radio-label .radio-indicator {
border-color: #4a90d9;
}
.radio-group input[type="radio"]:checked + .radio-label .radio-indicator::after {
opacity: 1;
}
.radio-text {
flex: 1;
}
.radio-text strong {
display: block;
font-size: 15px;
color: #333;
}
.radio-text small {
color: #999;
font-size: 13px;
}
.radio-price {
font-size: 18px;
font-weight: bold;
color: #333;
}
.radio-price .period {
font-size: 13px;
font-weight: normal;
color: #999;
}
/* 聚焦样式 */
.radio-group input[type="radio"]:focus-visible + .radio-label {
outline: 2px solid #4a90d9;
outline-offset: 2px;
}
/* 禁用状态 */
.radio-group input[type="radio"]:disabled + .radio-label {
opacity: 0.5;
cursor: not-allowed;
}
</style>
</head>
<body>
<h2>选择订阅计划</h2>
<form action="/subscribe" method="post">
<div class="radio-group">
<input type="radio" name="plan" id="free" value="free">
<label class="radio-label" for="free">
<span class="radio-indicator"></span>
<span class="radio-text">
<strong>免费版</strong>
<small>5GB 存储空间,基础功能</small>
</span>
<span class="radio-price">¥0<span class="period">/月</span></span>
</label>
<input type="radio" name="plan" id="pro" value="pro" checked>
<label class="radio-label" for="pro">
<span class="radio-indicator"></span>
<span class="radio-text">
<strong>专业版</strong>
<small>100GB 存储空间,高级功能</small>
</span>
<span class="radio-price">¥29<span class="period">/月</span></span>
</label>
<input type="radio" name="plan" id="enterprise" value="enterprise">
<label class="radio-label" for="enterprise">
<span class="radio-indicator"></span>
<span class="radio-text">
<strong>企业版</strong>
<small>无限存储空间,全部功能 + 技术支持</small>
</span>
<span class="radio-price">¥99<span class="period">/月</span></span>
</label>
</div>
<button type="submit" style="margin-top: 20px; padding: 12px 32px; background: #4a90d9; color: white; border: none; border-radius: 8px; font-size: 16px; cursor: pointer;">
立即订阅
</button>
</form>
</body>
</html>注意事项
必须有 value 属性:缺少
value时提交的值为"on",无法区分选项。同组 name 必须一致:拼写错误或大小写不同会导致互斥失败。
radio 不可取消选择:一旦选中,用户只能切换到同组的其他选项,无法回到"未选择"状态。如果需要"无选择"状态,添加一个"无"选项。
checked 只应设置一个:同组内多个
checked时,浏览器只认最后一个。但在 HTML 源码中不应出现多个 checked。required 验证整个组:
required只需设置在组内任意一个 radio 上。
最佳实践
始终使用
<label>关联 radio:扩大可点击区域,提升可用性和无障碍性。使用
<fieldset>+<legend>分组:为 radio group 提供组标题,帮助屏幕阅读器用户理解选项组。提供默认选中项:对大多数场景,设置一个合理的默认值,减少用户操作。
用 CSS 隐藏原生样式并自定义:原生 radio 的样式有限且跨浏览器不一致,通常需要自定义。
选项按逻辑顺序排列:如从小到大、从低到高,符合用户的认知习惯。
下一节
继续学习:checkbox 复选框