如何在 HTML 中创建单选按钮
单选按钮让用户在一组选项中只选一个。每个选项都是一个 <input type="radio">,给它们相同的 name,它们就成了一组。选择一个尺寸,然后点击 Order:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Radio buttons</title>
<style>
body { font-family: system-ui, sans-serif; }
fieldset { border: 1px solid #9ca3af; border-radius: 8px; }
label { display: block; margin: 4px 0; }
</style>
</head>
<body>
<form>
<fieldset>
<legend>Size</legend>
<label><input type="radio" name="size" value="s"> Small</label>
<label><input type="radio" name="size" value="m" checked> Medium</label>
<label><input type="radio" name="size" value="l"> Large</label>
</fieldset>
<p><button type="submit">Order</button></p>
</form>
</body>
</html>
表单只发送一对数据 size=m:组的 name 和选中按钮的 value。各个组成部分:
- 组内每个按钮都有相同的
name。选中一个,其他的就会取消选中。
- 每个按钮都有不同的
value。服务器收到的就是这个值。没有 value 的单选按钮会发送 on,服务器从中得不到任何信息。
- 默认选项上加
checked。
- 每个按钮外面包一个
<label>,让文字也能点击;再用 <fieldset> 加 <legend>,让屏幕阅读器在读出每个选项时连同问题(“Size”)一起读出。
name 决定分组
name 不同的单选按钮属于不同的组,每组各自有一个选中项。下面的例子有两组,另外还有一组出错的按钮:每个按钮都被误设了不同的 name:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Radio groups</title>
<style>
body { font-family: system-ui, sans-serif; display: flex; flex-wrap: wrap; gap: 16px; }
fieldset { border: 1px solid #9ca3af; border-radius: 8px; }
.broken { border-color: #dc2626; }
label { display: block; margin: 4px 0; }
</style>
</head>
<body>
<fieldset>
<legend>Crust</legend>
<label><input type="radio" name="crust" value="thin" checked> Thin</label>
<label><input type="radio" name="crust" value="thick"> Thick</label>
</fieldset>
<fieldset>
<legend>Sauce</legend>
<label><input type="radio" name="sauce" value="tomato" checked> Tomato</label>
<label><input type="radio" name="sauce" value="pesto"> Pesto</label>
</fieldset>
<fieldset class="broken">
<legend>Broken: different names</legend>
<label><input type="radio" name="a" value="1"> One</label>
<label><input type="radio" name="b" value="2"> Two</label>
<label><input type="radio" name="c" value="3"> Three</label>
</fieldset>
</body>
</html>
把出错那组的三个按钮都点一遍:它们全部保持选中,而且一个都取消不了,两边的缺点都占了。单选按钮在同一个表单内按 name 分组,所以两个表单可以各有一个 name="size" 的组。
必选组与不设默认值
组内任意一个单选按钮加上 required,就表示提交表单前必须选中其中一个。不写 checked 会让用户主动做出选择,在没有合理默认值时,这样做是对的:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Required radio group</title>
<style>
body { font-family: system-ui, sans-serif; }
fieldset { border: 1px solid #9ca3af; border-radius: 8px; }
label { display: block; margin: 4px 0; }
</style>
</head>
<body>
<form>
<fieldset>
<legend>How did you hear about us?</legend>
<label><input type="radio" name="source" value="search" required> Search engine</label>
<label><input type="radio" name="source" value="friend"> A friend</label>
<label><input type="radio" name="source" value="video"> A video</label>
<label><input type="radio" name="source" value="other"> Other</label>
</fieldset>
<p><button type="submit">Continue</button></p>
</form>
</body>
</html>
不做选择就点击 Continue,浏览器会阻止提交并显示提示信息。只要选中任意一个选项,整个组就有效了。没有选中且不是必选的组什么都不会发送,和未勾选的复选框一样。
点击可以选中单选按钮,但永远无法取消选中。如果在做出选择之后还需要能“不回答”,就专门加一个对应的选项。
在 JavaScript 中读取选中的值
form.elements.name 返回整个组,它的 .value 就是选中的单选按钮的值(一个都没选中时为空字符串)。change 事件会在变为选中的那个单选按钮上触发:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Selected value</title>
<style>
body { font-family: system-ui, sans-serif; }
label { margin-inline-end: 12px; }
#price { font-size: 1.4em; font-weight: bold; color: #2563eb; }
</style>
</head>
<body>
<form id="billing">
<label><input type="radio" name="period" value="monthly" checked> Monthly</label>
<label><input type="radio" name="period" value="yearly"> Yearly</label>
</form>
<p>Price: <span id="price"></span></p>
<script>
const form = document.querySelector('#billing');
const prices = { monthly: '12 per month', yearly: '96 per year' };
const show = () => {
document.querySelector('#price').textContent = prices[form.elements.period.value];
};
form.addEventListener('change', show);
show();
</script>
</body>
</html>
监听器绑定在表单上,所以表单中任何字段的变化都能捕获到。在表单之外,可以用 document.querySelector('input[name="period"]:checked') 找到选中的单选按钮。
键盘操作
一组单选按钮在 Tab 键顺序中只占一站。按 Tab 进入这一组时,焦点落在选中的按钮上(一个都没选中时落在第一个上),然后用方向键在选项之间切换选中项。再按一次 Tab 就离开这一组。只要使用共享同一个 name 的真正的 radio 输入框,这些行为都是现成的,这也是不要用 <div> 元素重新实现单选按钮的又一个理由。
单选按钮的样式
accent-color 可以改变原生单选按钮的颜色。appearance: none 让你自己画圆点。而且隐藏起来的单选按钮仍然可以通过它的标签选中,所以一组按钮可以看起来像一组卡片,同时保留单选按钮的全部行为:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Radio styles</title>
<style>
body { font-family: system-ui, sans-serif; }
.accent { accent-color: #16a34a; }
.dot {
appearance: none;
width: 18px;
height: 18px;
margin: 0;
border: 2px solid #6b7280;
border-radius: 50%;
vertical-align: middle;
}
.dot:checked { border: 6px solid #2563eb; }
.dot:focus-visible { outline: 3px solid #f59e0b; outline-offset: 2px; }
.cards { display: flex; flex-wrap: wrap; gap: 8px; border: 0; padding: 0; margin: 16px 0 0; }
.card { display: block; padding: 10px 14px; border: 2px solid #e5e7eb; border-radius: 10px; cursor: pointer; }
.card input { position: absolute; opacity: 0; }
.card:has(input:checked) { border-color: #2563eb; background: #eff6ff; }
.card:has(input:focus-visible) { outline: 3px solid #f59e0b; outline-offset: 2px; }
</style>
</head>
<body>
<label><input type="radio" class="accent" name="c1" checked> accent-color</label>
<label><input type="radio" class="accent" name="c1"> accent-color</label>
<p>
<label><input type="radio" class="dot" name="c2" checked> Custom</label>
<label><input type="radio" class="dot" name="c2"> Custom</label>
</p>
<fieldset class="cards">
<legend>Plan</legend>
<label class="card"><input type="radio" name="plan" value="free" checked> Free</label>
<label class="card"><input type="radio" name="plan" value="pro"> Pro</label>
<label class="card"><input type="radio" name="plan" value="team"> Team</label>
</fieldset>
</body>
</html>
自定义圆点其实只是选中时变粗的边框。卡片用 opacity: 0 而不是 display: none 来隐藏输入框,这样它仍然可以获得焦点,方向键也照常可用;:has(input:checked) 则为选中的卡片设置样式。当前版本的 Chrome、Firefox 和 Safari 都支持 :has()。
单选按钮、复选框还是下拉列表?
| 用什么 | 什么时候用 |
|---|
| 单选按钮 | 从少量选项(大约 2 到 6 个)中选一个,并且所有选项都应该直接可见 |
| 复选框 | 可以选任意多个,也可以一个都不选 |
<select> | 从一长串选项中选一个,比如国家(参见 HTML select) |
常见错误
- 同一组里用了不同的
name。每个单选按钮都能选中,却一个都取消不了。
- 没有
value。无论选了什么,服务器收到的都是 on。
- 在自定义设计中用
display: none 隐藏输入框。它将无法获得焦点,也无法用键盘操作。
- 没有
<fieldset> 和 <legend>。屏幕阅读器只会读出“Small, radio button”,却没有对应的问题。
- 用单个单选按钮做“我同意”勾选框。它一旦选中就无法取消;请使用复选框。
- 同一组的多个单选按钮都加了
checked。最终只有最后一个被选中。