如何在 HTML 中创建复选框
复选框就是 <input type="checkbox">。把它和它的文字一起放进 <label>,这样点击文字也能切换勾选状态。勾选其中一个或两个,再点击 Save,看看表单会发送什么:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Checkbox</title>
</head>
<body>
<form>
<p><label><input type="checkbox" name="newsletter" value="yes"> Send me the newsletter</label></p>
<p><label><input type="checkbox" name="updates" checked> Product updates</label></p>
<button type="submit">Save</button>
</form>
</body>
</html>
在默认状态下,表单只发送 updates=on。原因可以归结为两条规则:
- 勾选的复选框发送
name=value。 第二个复选框没有 value,所以它发送默认值 on。
- 未勾选的复选框什么都不发送。 既不是
newsletter=no,也不是空值:数据里根本没有这个名字。
HTML 中的 checked 设置初始状态。和所有布尔属性一样,起作用的是它是否存在:checked="false" 仍然会勾选复选框。
复选框的属性
| 属性 | 作用 |
|---|
name | 勾选时发送的键名 |
value | 勾选时发送的内容(默认是 on) |
checked | 页面加载时就处于勾选状态 |
required | 勾选这个复选框之前,表单无法提交 |
disabled | 无法切换,也不会被发送 |
一组复选框
对于“可以任选几项”的情况,给每个复选框相同的 name 和不同的 value。把它们包在带 <legend> 的 <fieldset> 里,让这一组有个名称:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Checkbox 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>Topics you like</legend>
<label><input type="checkbox" name="topic" value="html" checked> HTML</label>
<label><input type="checkbox" name="topic" value="css" checked> CSS</label>
<label><input type="checkbox" name="topic" value="js"> JavaScript</label>
</fieldset>
<p><button type="submit">Save</button></p>
</form>
</body>
</html>
表单发送的是 topic=html 和 topic=css:同一个名字出现两次。大多数服务器框架都能把它读取为一个列表。PHP 是个例外:除非名字以方括号结尾,即 name="topic[]",否则它只保留最后一个。在 JavaScript 中,new FormData(form).getAll('topic') 会返回 ['html', 'css']。
未勾选时也发送一个值
当服务器需要一个明确的“否”时,就在复选框之前放一个同名的隐藏输入框。未勾选时只发送隐藏输入框的值;勾选时两个值都会发送,服务器取最后一个:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Unchecked value</title>
</head>
<body>
<form>
<input type="hidden" name="marketing" value="no">
<label><input type="checkbox" name="marketing" value="yes"> Marketing emails</label>
<p>
<label><input type="checkbox" name="terms" value="accepted" required> I accept the terms</label>
</p>
<button type="submit">Continue</button>
</form>
</body>
</html>
点击 Continue:在勾选条款复选框之前,表单拒绝提交,因为它是 required 的。勾选后,表单发送 marketing=no 和 terms=accepted;如果第一个复选框也勾选了,就发送 marketing=no、marketing=yes 和 terms=accepted。Rails 等框架生成的正是这种隐藏输入框加复选框的组合。依赖这种写法之前,先确认你的服务器如何读取重复的名字。
全选与 indeterminate 状态
复选框还有第三种外观,一条短横线,表示“部分勾选”。它只能通过 JavaScript 设置:box.indeterminate = true。当“全选”复选框控制的复选框中有一部分被勾选时,就会用到它:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Select all</title>
<style>
body { font-family: system-ui, sans-serif; }
ul { list-style: none; padding-inline-start: 24px; margin: 4px 0; }
</style>
</head>
<body>
<label><input type="checkbox" id="all"> Select all</label>
<ul>
<li><label><input type="checkbox" class="item" checked> Email</label></li>
<li><label><input type="checkbox" class="item"> SMS</label></li>
<li><label><input type="checkbox" class="item"> Push</label></li>
</ul>
<script>
const all = document.querySelector('#all');
const items = [...document.querySelectorAll('.item')];
const sync = () => {
const count = items.filter((box) => box.checked).length;
all.checked = count === items.length;
all.indeterminate = count > 0 && count < items.length;
};
all.addEventListener('change', () => {
items.forEach((box) => { box.checked = all.checked; });
});
items.forEach((box) => box.addEventListener('change', sync));
sync();
</script>
</body>
</html>
indeterminate 只改变外观(以及屏幕阅读器的播报内容:“混合”或“部分选中”)。复选框在底层仍然只有勾选或未勾选两种状态,表单发送的也是这个状态。点击处于 indeterminate 状态的复选框会清除短横线并切换勾选状态。
用 change 事件响应复选框的变化,读取 box.checked 获取它的状态。切换复选框时,box.value 不会改变。
设置复选框样式
最快的改法是 accent-color,它会给原生复选框换颜色,同时保留它的所有行为。想要完全不同的外观,appearance: none 会去掉原生的绘制效果,让你可以像设置任何盒子一样设置这个输入框的样式:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Checkbox styles</title>
<style>
body { font-family: system-ui, sans-serif; }
label { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.accent { accent-color: #16a34a; width: 20px; height: 20px; }
.custom {
appearance: none;
width: 20px;
height: 20px;
margin: 0;
border: 2px solid #6b7280;
border-radius: 5px;
display: grid;
place-content: center;
}
.custom::before {
content: "";
width: 10px;
height: 6px;
border: 3px solid #fff;
border-top: 0;
border-right: 0;
transform: rotate(-45deg) translate(1px, -1px);
visibility: hidden;
}
.custom:checked { background: #2563eb; border-color: #2563eb; }
.custom:checked::before { visibility: visible; }
.custom:focus-visible { outline: 3px solid #f59e0b; outline-offset: 2px; }
</style>
</head>
<body>
<label><input type="checkbox" class="accent" checked> accent-color</label>
<label><input type="checkbox" class="custom" checked> appearance: none</label>
<label><input type="checkbox" class="custom"> appearance: none, unchecked</label>
</body>
</html>
自定义的复选框仍然是真正的复选框:点击或按空格键都能切换,会随表单发送,也会被播报为复选框。用 :checked 绘制勾选状态,并保留 :focus-visible 样式,因为去掉原生绘制后,除了你自己添加的,不会再有任何焦点环。
开关按钮
开关是一种表示“现在打开还是关闭”的复选框,而不是“是否包含这一项”。用 appearance: none 绘制它,并加上 role="switch",这样屏幕阅读器播报的是一个打开或关闭的开关,而不是一个已勾选的复选框:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Toggle switch</title>
<style>
body { font-family: system-ui, sans-serif; }
label { display: inline-flex; align-items: center; gap: 10px; }
.switch {
appearance: none;
margin: 0;
width: 44px;
height: 24px;
border-radius: 12px;
background: #9ca3af;
position: relative;
cursor: pointer;
transition: background 0.2s;
}
.switch::before {
content: "";
position: absolute;
top: 3px;
inset-inline-start: 3px;
width: 18px;
height: 18px;
border-radius: 50%;
background: #fff;
transition: inset-inline-start 0.2s;
}
.switch:checked { background: #16a34a; }
.switch:checked::before { inset-inline-start: 23px; }
.switch:focus-visible { outline: 3px solid #f59e0b; outline-offset: 2px; }
</style>
</head>
<body>
<label><input type="checkbox" class="switch" role="switch" name="dark" checked> Dark mode</label>
</body>
</html>
inset-inline-start 从行的起始边开始放置滑块,所以在从右到左书写的页面上,开关会朝另一个方向滑动,这正是应有的效果。对于只能选一个的成组是否选项,请参阅 HTML 单选按钮。
常见错误
- 以为未勾选的复选框会发送
false。 什么都不会发送。在服务器上把缺失的名字当作“关闭”处理,或者加上隐藏输入框。
checked="false"。 它仍然会勾选复选框。删掉这个属性。
- 复选框没有 label,或者旁边的文字不是
<label>。这样小小的方框就成了唯一的点击区域,屏幕阅读器也只会播报“复选框”,没有名称。请参阅 HTML label。
- 忘了写
value,结果一组复选框都发送 on,服务器无法区分它们。
- 读取
box.value 来判断是否勾选。 请使用 box.checked。
- 用复选框做“单选”。 那是单选按钮的用途。