checkbox 复选框
type="checkbox" 创建复选框控件,允许用户独立地选择或取消选择多个选项。与 radio 的互斥选择不同,checkbox 中的每个选项都是独立的,用户可以同时选中任意数量的选项。本节将详细讲解复选框的多选机制、checked 默认选中、indeterminate 三态,以及 label 关联的最佳实践。
前置知识
阅读本节前,建议先了解:radio 单选按钮
基础概念
什么是复选框
复选框(checkbox)是一种多选控件,呈现为小方框。用户点击时在选中(勾选)和未选中之间切换。同 name 的多个 checkbox 可以同时被选中,各自独立。
<!-- 兴趣爱好多选 -->
<label><input type="checkbox" name="hobby" value="reading"> 阅读</label>
<label><input type="checkbox" name="hobby" value="music"> 音乐</label>
<label><input type="checkbox" name="hobby" value="sports"> 运动</label>
<!-- 三个可以同时选中 -->checkbox 与 radio 对比
| 对比维度 | checkbox(复选框) | radio(单选按钮) |
|---|---|---|
| 选择数量 | 0~N 个 | 0~1 个 |
| 互斥性 | 不互斥,各自独立 | 同 name 互斥 |
| 可取消 | 是(点击可取消选中) | 否(只能切换到其他选项) |
| 外观 | 方框 + 勾 | 圆圈 + 点 |
| name 相同时 | 提交多个同名键值对 | 提交一个键值对 |
checkbox 的核心特征
| 特征 | 说明 |
|---|---|
| 选择方式 | 点击(或键盘空格) |
| 多选 | 可以选中任意数量的选项 |
| 取消 | 点击已选中的可取消 |
| 提交值 | 选中项的 value(未选中的不提交) |
| 三态 | 选中、未选中、不确定(indeterminate) |
语法
基本语法
<input
type="checkbox"
name="hobby"
id="reading"
value="reading"
checked
disabled
>
<label for="reading">阅读</label>属性说明
| 属性 | 值 | 说明 |
|---|---|---|
type | "checkbox" | 指定为复选框 |
name | 字符串 | 字段名 |
value | 字符串 | 选中时提交的值 |
checked | 布尔属性 | 默认选中 |
disabled | 布尔属性 | 禁用该选项 |
id | 字符串 | 唯一标识,用于 label 关联 |
required | 布尔属性 | 必须选中(仅对单个 checkbox 有效) |
详细说明
多选机制
同一 name 的多个 checkbox 各自独立,可以同时选中多个。提交时,每个选中的 checkbox 都会生成一个键值对。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>checkbox 多选</title>
</head>
<body>
<h2>选择你的技能</h2>
<form action="/profile" method="post">
<!--
同 name 的多个 checkbox 可以同时选中
每个选中的都会作为单独的键值对提交
-->
<label><input type="checkbox" name="skill" value="html"> HTML</label><br>
<label><input type="checkbox" name="skill" value="css"> CSS</label><br>
<label><input type="checkbox" name="skill" value="js"> JavaScript</label><br>
<label><input type="checkbox" name="skill" value="python"> Python</label><br>
<label><input type="checkbox" name="skill" value="java"> Java</label><br>
<button type="submit">提交</button>
</form>
<!--
假设用户选中了 HTML、CSS、JavaScript
提交的数据:
skill=html&skill=css&skill=js
服务器端获取数组:
skill = ["html", "css", "js"]
-->
</body>
</html>未选中的 checkbox 不提交
与 radio 不同,未选中的 checkbox 不会随表单提交。如果用户没有选择任何技能,服务器端不会收到 skill 字段。
checked 默认选中
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>checked 默认选中</title>
</head>
<body>
<h2>通知设置</h2>
<form action="/settings" method="post">
<!-- 默认选中 -->
<label>
<input type="checkbox" name="notify_email" value="1" checked>
邮件通知
</label><br>
<!-- 默认选中 -->
<label>
<input type="checkbox" name="notify_sms" value="1" checked>
短信通知
</label><br>
<!-- 默认不选中 -->
<label>
<input type="checkbox" name="notify_push" value="1">
推送通知
</label><br>
<!-- 默认不选中 -->
<label>
<input type="checkbox" name="notify_weekly" value="1">
周报邮件
</label>
<button type="submit">保存设置</button>
</form>
</body>
</html>label 关联
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>checkbox label 关联</title>
<style>
/* 方式一:label 包裹 checkbox */
.checkbox-label {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
margin: 4px 0;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.2s;
}
.checkbox-label:hover {
background-color: #f0f0f0;
}
input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
}
</style>
</head>
<body>
<h2>同意条款</h2>
<!--
方式一:label 包裹(推荐)
点击整行都能切换 checkbox
-->
<label class="checkbox-label">
<input type="checkbox" name="agree" value="terms">
我已阅读并同意《用户协议》和《隐私政策》
</label>
<!--
方式二:for + id
-->
<input type="checkbox" name="newsletter" value="1" id="newsletter">
<label for="newsletter">订阅新闻邮件</label>
</body>
</html>indeterminate 三态
indeterminate(不确定状态)是一种只能通过 JavaScript 设置的视觉状态,表示"部分选中"。它通常用于"全选"父级 checkbox,当子选项部分被选中时显示为不确定状态。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>indeterminate 三态</title>
<style>
.checkbox-group {
padding: 20px;
border: 1px solid #e0e0e0;
border-radius: 8px;
max-width: 400px;
}
.parent-checkbox {
font-weight: 600;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid #f0f0f0;
}
.child-checkbox {
margin-left: 24px;
margin-bottom: 6px;
}
label {
cursor: pointer;
display: block;
margin-bottom: 4px;
}
</style>
</head>
<body>
<h2>全选/取消全选</h2>
<div class="checkbox-group">
<!-- 父级:全选 -->
<div class="parent-checkbox">
<label>
<input type="checkbox" id="selectAll">
<strong>全选</strong>
</label>
</div>
<!-- 子级 -->
<div class="child-checkbox">
<label>
<input type="checkbox" name="item" value="html" class="child"> HTML
</label>
<label>
<input type="checkbox" name="item" value="css" class="child"> CSS
</label>
<label>
<input type="checkbox" name="item" value="js" class="child"> JavaScript
</label>
<label>
<input type="checkbox" name="item" value="python" class="child"> Python
</label>
<label>
<input type="checkbox" name="item" value="java" class="child"> Java
</label>
</div>
</div>
<script>
const selectAll = document.getElementById('selectAll');
const children = document.querySelectorAll('.child');
// 更新父级 checkbox 状态
function updateParent() {
const checkedCount = document.querySelectorAll('.child:checked').length;
const totalCount = children.length;
if (checkedCount === 0) {
// 全部未选中
selectAll.checked = false;
selectAll.indeterminate = false;
} else if (checkedCount === totalCount) {
// 全部选中
selectAll.checked = true;
selectAll.indeterminate = false;
} else {
// 部分选中 → 不确定状态
selectAll.checked = false;
selectAll.indeterminate = true;
}
}
// 点击父级 checkbox
selectAll.addEventListener('change', function() {
const isChecked = this.checked;
children.forEach(child => {
child.checked = isChecked;
});
// 不需要调用 updateParent,因为全选/全不选状态明确
});
// 点击子级 checkbox
children.forEach(child => {
child.addEventListener('change', updateParent);
});
// 初始化状态
updateParent();
</script>
</body>
</html>indeterminate 不是 HTML 属性
indeterminate 不能通过 HTML 属性设置(<input indeterminate> 无效)。它只能通过 JavaScript 的 checkbox.indeterminate = true 来设置。它只影响视觉显示,不影响 checked 属性和表单提交。提交时 indeterminate 的 checkbox 值取决于 checked 是 true 还是 false。
单个 checkbox 用作开关
checkbox 经常被用作开/关切换,此时通常只有一个 checkbox,value 为 "1" 或 "yes"。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>checkbox 开关</title>
</head>
<body>
<h2>账号设置</h2>
<form action="/settings" method="post">
<!-- 单个 checkbox 作为开关 -->
<label>
<input type="checkbox" name="dark_mode" value="1">
深色模式
</label><br>
<label>
<input type="checkbox" name="two_factor" value="1" checked>
两步验证
</label><br>
<label>
<input type="checkbox" name="public_profile" value="1">
公开个人资料
</label><br>
<!--
提交逻辑:
- 选中 dark_mode:提交 dark_mode=1
- 未选中 dark_mode:不提交 dark_mode 字段
- 服务器通过是否存在该字段来判断开关状态
-->
<button type="submit">保存</button>
</form>
</body>
</html>JavaScript 操作 checkbox
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>JS 操作 checkbox</title>
</head>
<body>
<h2>技能选择</h2>
<form id="skillForm">
<label><input type="checkbox" name="skill" value="html" class="skill"> HTML</label><br>
<label><input type="checkbox" name="skill" value="css" class="skill"> CSS</label><br>
<label><input type="checkbox" name="skill" value="js" class="skill"> JavaScript</label><br>
<label><input type="checkbox" name="skill" value="python" class="skill"> Python</label><br>
</form>
<div style="margin-top: 16px;">
<button type="button" id="btnSelectAll">全选</button>
<button type="button" id="btnDeselectAll">全不选</button>
<button type="button" id="btnInvert">反选</button>
<button type="button" id="btnGetValues">获取选中值</button>
</div>
<div id="output" style="margin-top: 12px; padding: 12px; background: #f5f5f5; border-radius: 8px;"></div>
<script>
const checkboxes = document.querySelectorAll('.skill');
const output = document.getElementById('output');
// 全选
document.getElementById('btnSelectAll').addEventListener('click', () => {
checkboxes.forEach(cb => cb.checked = true);
});
// 全不选
document.getElementById('btnDeselectAll').addEventListener('click', () => {
checkboxes.forEach(cb => cb.checked = false);
});
// 反选
document.getElementById('btnInvert').addEventListener('click', () => {
checkboxes.forEach(cb => cb.checked = !cb.checked);
});
// 获取选中的值
document.getElementById('btnGetValues').addEventListener('click', () => {
const selected = [];
checkboxes.forEach(cb => {
if (cb.checked) selected.push(cb.value);
});
output.textContent = '选中项:' + (selected.length ? selected.join(', ') : '无');
});
// 使用 FormData 获取所有选中值
document.getElementById('btnGetValues').addEventListener('click', () => {
const form = document.getElementById('skillForm');
const formData = new FormData(form);
const values = formData.getAll('skill');
console.log('FormData 获取:', values);
});
</script>
</body>
</html>实战示例
自定义样式的开关式 checkbox
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>开关式 checkbox</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
max-width: 500px;
margin: 40px auto;
padding: 20px;
}
h2 { margin-bottom: 24px; }
.setting-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 0;
border-bottom: 1px solid #f0f0f0;
}
.setting-info {
flex: 1;
}
.setting-info strong {
display: block;
font-size: 15px;
color: #333;
}
.setting-info small {
color: #999;
font-size: 13px;
}
/* 开关样式 */
.switch {
position: relative;
display: inline-block;
width: 48px;
height: 26px;
flex-shrink: 0;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
border-radius: 26px;
transition: 0.3s;
}
.slider::before {
content: '';
position: absolute;
height: 20px;
width: 20px;
left: 3px;
bottom: 3px;
background-color: white;
border-radius: 50%;
transition: 0.3s;
}
.switch input:checked + .slider {
background-color: #4a90d9;
}
.switch input:checked + .slider::before {
transform: translateX(22px);
}
.switch input:focus-visible + .slider {
outline: 2px solid #4a90d9;
outline-offset: 2px;
}
/* 禁用状态 */
.switch input:disabled + .slider {
opacity: 0.5;
cursor: not-allowed;
}
</style>
</head>
<body>
<h2>通知设置</h2>
<form action="/settings/notifications" method="post">
<div class="setting-item">
<div class="setting-info">
<strong>邮件通知</strong>
<small>接收项目更新和团队消息的邮件</small>
</div>
<label class="switch">
<input type="checkbox" name="email_notify" value="1" checked>
<span class="slider"></span>
</label>
</div>
<div class="setting-item">
<div class="setting-info">
<strong>短信通知</strong>
<small>接收安全验证和重要提醒的短信</small>
</div>
<label class="switch">
<input type="checkbox" name="sms_notify" value="1">
<span class="slider"></span>
</label>
</div>
<div class="setting-item">
<div class="setting-info">
<strong>桌面推送</strong>
<small>在桌面端接收实时推送通知</small>
</div>
<label class="switch">
<input type="checkbox" name="desktop_notify" value="1" checked>
<span class="slider"></span>
</label>
</div>
<div class="setting-item">
<div class="setting-info">
<strong>营销邮件</strong>
<small>接收促销活动和产品推荐的邮件</small>
</div>
<label class="switch">
<input type="checkbox" name="marketing" value="1">
<span class="slider"></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 属性:与 radio 一样,缺少
value时提交的值为"on"。未选中的不提交:未选中的 checkbox 不会出现在表单数据中。服务器需要通过字段是否存在来判断是否选中。
indeterminate 只能通过 JS 设置:HTML 不支持
indeterminate属性,只能用 JavaScript 设置,且不影响表单提交值。单个 checkbox 的 required:如果只有一个 checkbox 并设置了
required,用户必须选中它才能提交。如果有多个同 name 的 checkbox,required意味着至少选中一个。表单重置恢复 checked:
<button type="reset">会将 checkbox 恢复到 HTML 中的初始checked状态。
最佳实践
始终用 label 包裹 checkbox:扩大可点击区域,提升可用性。
开关场景使用自定义样式:对于"开启/关闭"类设置,使用 Toggle Switch 样式比原生 checkbox 更直观。
处理未提交的情况:在服务器端,通过检查字段是否存在来判断 checkbox 是否被选中,而非检查值是否为特定值。
全选功能使用 indeterminate:当子选项部分选中时,父级显示不确定状态,提供更好的视觉反馈。
避免过多选项:如果选项超过 7~8 个,考虑使用
<select multiple>或其他交互方式。
下一节
继续学习:select 下拉选择