Skip to content

checkbox 复选框

type="checkbox" 创建复选框控件,允许用户独立地选择或取消选择多个选项。与 radio 的互斥选择不同,checkbox 中的每个选项都是独立的,用户可以同时选中任意数量的选项。本节将详细讲解复选框的多选机制、checked 默认选中、indeterminate 三态,以及 label 关联的最佳实践。

前置知识

阅读本节前,建议先了解:radio 单选按钮

基础概念

什么是复选框

复选框(checkbox)是一种多选控件,呈现为小方框。用户点击时在选中(勾选)和未选中之间切换。同 name 的多个 checkbox 可以同时被选中,各自独立。

html
<!-- 兴趣爱好多选 -->
<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)

语法

基本语法

html
<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 都会生成一个键值对。

html
<!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 默认选中

html
<!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 关联

html
<!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,当子选项部分被选中时显示为不确定状态。

html
<!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 值取决于 checkedtrue 还是 false

单个 checkbox 用作开关

checkbox 经常被用作开/关切换,此时通常只有一个 checkbox,value"1""yes"

html
<!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

html
<!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

html
<!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>

注意事项

  1. 必须有 value 属性:与 radio 一样,缺少 value 时提交的值为 "on"

  2. 未选中的不提交:未选中的 checkbox 不会出现在表单数据中。服务器需要通过字段是否存在来判断是否选中。

  3. indeterminate 只能通过 JS 设置:HTML 不支持 indeterminate 属性,只能用 JavaScript 设置,且不影响表单提交值。

  4. 单个 checkbox 的 required:如果只有一个 checkbox 并设置了 required,用户必须选中它才能提交。如果有多个同 name 的 checkbox,required 意味着至少选中一个。

  5. 表单重置恢复 checked<button type="reset"> 会将 checkbox 恢复到 HTML 中的初始 checked 状态。

最佳实践

  1. 始终用 label 包裹 checkbox:扩大可点击区域,提升可用性。

  2. 开关场景使用自定义样式:对于"开启/关闭"类设置,使用 Toggle Switch 样式比原生 checkbox 更直观。

  3. 处理未提交的情况:在服务器端,通过检查字段是否存在来判断 checkbox 是否被选中,而非检查值是否为特定值。

  4. 全选功能使用 indeterminate:当子选项部分选中时,父级显示不确定状态,提供更好的视觉反馈。

  5. 避免过多选项:如果选项超过 7~8 个,考虑使用 <select multiple> 或其他交互方式。

下一节

继续学习:select 下拉选择

参考链接