Skip to content

optgroup 选项分组

<optgroup><select> 元素内部的分组容器,用于将相关的 <option> 选项组织成逻辑分组,每组有一个不可选择的标题。通过分组,用户可以更快地在大量选项中定位目标。本节将详细讲解 <optgroup> 的语法、label 分组标题、disabled 禁用分组,以及分组在无障碍访问中的作用。

前置知识

阅读本节前,建议先了解:datalist 数据列表

基础概念

什么是 optgroup

<optgroup><select> 的子元素,用于将 <option> 选项按类别分组。每个 <optgroup> 通过 label 属性设置组标题,该标题以不可选择的粗体文本形式显示在下拉列表中。

html
<select name="category">
  <optgroup label="前端技术">
    <option value="html">HTML</option>
    <option value="css">CSS</option>
    <option value="javascript">JavaScript</option>
  </optgroup>

  <optgroup label="后端技术">
    <option value="nodejs">Node.js</option>
    <option value="python">Python</option>
    <option value="java">Java</option>
  </optgroup>
</select>

optgroup 的核心特征

特征说明
位置必须在 <select> 内部
子元素只能包含 <option>
标题通过 label 属性设置,不可选择
嵌套不支持嵌套(不支持多级分组)
禁用可设置 disabled 禁用整组选项
提交不提交任何数据(不是表单控件)

语法

基本语法

html
<select name="fieldName">
  <!-- 无分组的选项 -->
  <option value="">请选择</option>

  <!-- 分组一 -->
  <optgroup label="分组标题1">
    <option value="v1">选项一</option>
    <option value="v2">选项二</option>
  </optgroup>

  <!-- 分组二 -->
  <optgroup label="分组标题2" disabled>
    <option value="v3">选项三</option>
    <option value="v4">选项四</option>
  </optgroup>
</select>

属性说明

属性说明
label字符串必需,分组标题文本
disabled布尔属性禁用整个分组

详细说明

label 分组标题

label 属性是 <optgroup>必需属性,设置分组的标题。标题以下拉列表中的粗体/斜体文本显示(不同浏览器样式略有差异),用户无法点击选择。

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>optgroup label</title>
  <style>
    .form-group {
      margin-bottom: 20px;
    }

    label {
      display: block;
      font-weight: 500;
      margin-bottom: 6px;
    }

    select {
      padding: 10px 12px;
      border: 1px solid #ccc;
      border-radius: 6px;
      font-size: 14px;
      min-width: 250px;
    }
  </style>
</head>
<body>
  <h2>选择技术方向</h2>
  <form action="/course" method="post">
    <div class="form-group">
      <label for="tech">技术分类:</label>
      <select id="tech" name="tech" required>
        <option value="">请选择技术方向</option>

        <!--
          label 是分组标题
          在下拉列表中显示为不可选的粗体文本
        -->
        <optgroup label="前端开发">
          <option value="html-css">HTML / CSS</option>
          <option value="javascript">JavaScript 基础</option>
          <option value="vue">Vue.js</option>
          <option value="react">React</option>
          <option value="angular">Angular</option>
        </optgroup>

        <optgroup label="后端开发">
          <option value="nodejs">Node.js</option>
          <option value="python">Python</option>
          <option value="java">Java</option>
          <option value="go">Go</option>
          <option value="rust">Rust</option>
        </optgroup>

        <optgroup label="移动开发">
          <option value="ios-swift">iOS (Swift)</option>
          <option value="android-kotlin">Android (Kotlin)</option>
          <option value="flutter">Flutter</option>
          <option value="react-native">React Native</option>
        </optgroup>

        <optgroup label="DevOps">
          <option value="docker">Docker</option>
          <option value="kubernetes">Kubernetes</option>
          <option value="ci-cd">CI/CD</option>
        </optgroup>
      </select>
    </div>

    <button type="submit">开始学习</button>
  </form>
</body>
</html>

disabled 禁用分组

<optgroup>disabled 属性会禁用整个分组中的所有选项。被禁用的选项在下拉列表中显示为灰色,用户无法选择。

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>optgroup disabled</title>
  <style>
    select {
      padding: 10px;
      border: 1px solid #ccc;
      border-radius: 6px;
      font-size: 14px;
      min-width: 300px;
    }
  </style>
</head>
<body>
  <h2>选择配送方式</h2>
  <form action="/order" method="post">
    <label for="shipping">配送方式:</label>
    <select id="shipping" name="shipping" required>
      <optgroup label="标准配送">
        <option value="standard" selected>标准快递(3~5天,免费)</option>
        <option value="express">加急快递(1~2天,¥15)</option>
      </optgroup>

      <!--
        禁用整个分组
        可能是暂时不可用的服务
      -->
      <optgroup label="特殊配送" disabled>
        <option value="sameday">当日达(¥30)</option>
        <option value="scheduled">定时达(¥20)</option>
        <option value="pickup">门店自提(免费)</option>
      </optgroup>

      <optgroup label="国际配送">
        <option value="intl-standard">国际标准(7~15天,¥50起)</option>
        <option value="intl-express">国际加急(3~5天,¥100起)</option>
      </optgroup>
    </select>

    <button type="submit">提交订单</button>
  </form>

  <!--
    用户看到的效果:
    - "特殊配送" 分组标题和所有选项都显示为灰色
    - 无法选择 "当日达"、"定时达"、"门店自提"
    - 其他分组的选项正常可选
  -->
</body>
</html>

optgroup disabled 与单个 option disabled

  • optgroup disabled 禁用整个组
  • 单个 <option disabled> 禁用单个选项
  • 如果一个 <option> 既在 disabled 的 optgroup 中,自身也设置了 disabled,效果相同
  • 可以通过 JavaScript 动态修改 disabled 属性来启用/禁用分组

混合使用:有分组和无分组选项

<select> 中可以同时存在无分组的 <option> 和有分组的 <optgroup>

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>混合分组</title>
  <style>
    select {
      padding: 10px;
      border: 1px solid #ccc;
      border-radius: 6px;
      font-size: 14px;
      min-width: 300px;
    }
  </style>
</head>
<body>
  <h2>字体选择</h2>
  <form>
    <label for="font">选择字体:</label>
    <select id="font" name="font">
      <!-- 无分组的默认选项 -->
      <option value="">使用默认字体</option>
      <option value="system-ui">系统默认 (system-ui)</option>

      <!-- 有分组的选项 -->
      <optgroup label="无衬线字体">
        <option value="arial">Arial</option>
        <option value="helvetica">Helvetica</option>
        <option value="verdana">Verdana</option>
        <option value="tahoma">Tahoma</option>
      </optgroup>

      <optgroup label="衬线字体">
        <option value="times">Times New Roman</option>
        <option value="georgia">Georgia</option>
        <option value="palatino">Palatino</option>
      </optgroup>

      <optgroup label="等宽字体">
        <option value="courier">Courier New</option>
        <option value="consolas">Consolas</option>
        <option value="monaco">Monaco</option>
      </optgroup>

      <optgroup label="中文字体">
        <option value="simhei">黑体 (SimHei)</option>
        <option value="simsun">宋体 (SimSun)</option>
        <option value="microsoftyahei">微软雅黑</option>
        <option value="fangsong">仿宋 (FangSong)</option>
      </optgroup>
    </select>
  </form>
</body>
</html>

JavaScript 操作 optgroup

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>JS 操作 optgroup</title>
  <style>
    select {
      padding: 10px;
      border: 1px solid #ccc;
      border-radius: 6px;
      font-size: 14px;
      min-width: 300px;
    }

    button {
      margin: 4px;
      padding: 6px 14px;
      border: 1px solid #ccc;
      border-radius: 4px;
      cursor: pointer;
    }

    #output {
      margin-top: 16px;
      padding: 12px;
      background: #f5f5f5;
      border-radius: 8px;
      font-family: monospace;
      white-space: pre-wrap;
    }
  </style>
</head>
<body>
  <h2>操作 optgroup</h2>
  <form>
    <label for="product">选择产品:</label>
    <select id="product" name="product">
      <optgroup label="手机">
        <option value="iphone15">iPhone 15</option>
        <option value="iphone15pro">iPhone 15 Pro</option>
        <option value="pixel8">Pixel 8</option>
      </optgroup>

      <optgroup label="笔记本">
        <option value="macbook-air">MacBook Air</option>
        <option value="macbook-pro">MacBook Pro</option>
        <option value="thinkpad">ThinkPad X1</option>
      </optgroup>

      <optgroup id="tabletGroup" label="平板">
        <option value="ipad">iPad</option>
        <option value="ipad-pro">iPad Pro</option>
        <option value="surface">Surface Pro</option>
      </optgroup>
    </select>
  </form>

  <div style="margin-top: 12px;">
    <button type="button" id="btnDisableTablet">禁用"平板"分组</button>
    <button type="button" id="btnEnableTablet">启用"平板"分组</button>
    <button type="button" id="btnAddGroup">添加"配件"分组</button>
    <button type="button" id="btnListGroup">获取所有分组信息</button>
  </div>

  <div id="output"></div>

  <script>
    const select = document.getElementById('product');
    const output = document.getElementById('output');

    // 禁用分组
    document.getElementById('btnDisableTablet').addEventListener('click', () => {
      document.getElementById('tabletGroup').disabled = true;
      output.textContent = '已禁用"平板"分组';
    });

    // 启用分组
    document.getElementById('btnEnableTablet').addEventListener('click', () => {
      document.getElementById('tabletGroup').disabled = false;
      output.textContent = '已启用"平板"分组';
    });

    // 动态添加分组
    document.getElementById('btnAddGroup').addEventListener('click', () => {
      const optgroup = document.createElement('optgroup');
      optgroup.label = '配件';
      optgroup.id = 'accessoryGroup';

      const items = [
        { value: 'airpods', text: 'AirPods' },
        { value: 'case', text: '手机壳' },
        { value: 'charger', text: '充电器' }
      ];

      items.forEach(item => {
        const option = document.createElement('option');
        option.value = item.value;
        option.text = item.text;
        optgroup.appendChild(option);
      });

      select.appendChild(optgroup);
      output.textContent = '已添加"配件"分组,包含 ' + items.length + ' 个选项';
    });

    // 获取所有分组信息
    document.getElementById('btnListGroup').addEventListener('click', () => {
      const groups = select.querySelectorAll('optgroup');
      let info = '';

      groups.forEach(group => {
        const options = group.querySelectorAll('option');
        info += `分组:${group.label}\n`;
        info += `  禁用:${group.disabled}\n`;
        info += `  选项数:${options.length}\n`;
        options.forEach(opt => {
          info += `    - ${opt.value}: ${opt.text}\n`;
        });
        info += '\n';
      });

      output.textContent = info;
    });
  </script>
</body>
</html>

无障碍访问

<optgroup> 对屏幕阅读器有重要意义,它提供了分组级别的语义信息:

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>optgroup 无障碍</title>
</head>
<body>
  <h2>无障碍友好的分组选择</h2>
  <form action="/order" method="post">

    <!--
      对于屏幕阅读器用户:
      - 使用方向键浏览选项时,会先朗读 optgroup 的 label
      - 例如:"前端开发 分组,HTML"
      - 帮助用户理解选项的上下文
    -->

    <!--
      配合 fieldset + legend 提供完整的语义结构:
      1. fieldset/legend 描述整个选择区域的含义
      2. optgroup/label 描述每个分组的含义
      3. option 的文本描述具体选项
    -->
    <fieldset>
      <legend>选择课程分类</legend>

      <label for="course">课程:</label>
      <select id="course" name="course" required>
        <option value="">-- 请选择课程 --</option>

        <optgroup label="前端开发">
          <option value="fe-html">HTML5 入门</option>
          <option value="fe-css">CSS3 进阶</option>
          <option value="fe-js">JavaScript 高级</option>
        </optgroup>

        <optgroup label="后端开发">
          <option value="be-node">Node.js 实战</option>
          <option value="be-python">Python Web 开发</option>
        </optgroup>

        <optgroup label="数据库">
          <option value="db-mysql">MySQL 入门</option>
          <option value="db-mongo">MongoDB 实战</option>
        </optgroup>
      </select>
    </fieldset>

    <button type="submit">选择课程</button>
  </form>
</body>
</html>

optgroup 对辅助技术的重要性

屏幕阅读器在朗读 <select> 选项时,会先朗读当前所在 <optgroup>label,然后朗读 <option> 的文本。这帮助视障用户理解选项属于哪个分类。如果省略 label,分组标题为空,辅助技术可能跳过分组信息或朗读不明确的内容。

实战示例

省市区三级选择(带分组)

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>省市区三级选择</title>
  <style>
    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
      max-width: 600px;
      margin: 40px auto;
      padding: 20px;
    }

    .form-group {
      margin-bottom: 16px;
    }

    label {
      display: block;
      font-weight: 500;
      margin-bottom: 6px;
    }

    select {
      width: 100%;
      padding: 10px 12px;
      border: 1px solid #ddd;
      border-radius: 6px;
      font-size: 14px;
      box-sizing: border-box;
    }

    .select-row {
      display: flex;
      gap: 12px;
    }

    .select-row .form-group {
      flex: 1;
    }
  </style>
</head>
<body>
  <h2>选择收货地址</h2>
  <form action="/address" method="post">
    <!-- 第一级:省份 -->
    <div class="form-group">
      <label for="province">省份/直辖市</label>
      <select id="province" name="province" required>
        <option value="">请选择省份</option>
      </select>
    </div>

    <!-- 第二级:城市(使用 optgroup 按地区分组) -->
    <div class="form-group">
      <label for="city">城市</label>
      <select id="city" name="city" required disabled>
        <option value="">请先选择省份</option>
      </select>
    </div>

    <!-- 第三级:区域 -->
    <div class="form-group">
      <label for="district">区/县</label>
      <select id="district" name="district" required disabled>
        <option value="">请先选择城市</option>
      </select>
    </div>

    <button type="submit">保存地址</button>
  </form>

  <script>
    // 省市区数据(简化版)
    const addressData = {
      '北京市': {
        '北京市': ['东城区', '西城区', '朝阳区', '海淀区', '丰台区', '通州区']
      },
      '广东省': {
        '广州': ['天河区', '海珠区', '越秀区', '白云区', '番禺区'],
        '深圳': ['南山区', '福田区', '罗湖区', '宝安区', '龙岗区'],
        '东莞': ['莞城区', '南城区', '东城区', '万江区', '松山湖']
      },
      '浙江省': {
        '杭州': ['西湖区', '上城区', '拱墅区', '滨江区', '余杭区'],
        '宁波': ['海曙区', '江北区', '鄞州区', '镇海区', '北仑区'],
        '温州': ['鹿城区', '龙湾区', '瓯海区', '乐清市', '瑞安市']
      }
    };

    const provinceSelect = document.getElementById('province');
    const citySelect = document.getElementById('city');
    const districtSelect = document.getElementById('district');

    // 初始化省份
    Object.keys(addressData).forEach(province => {
      const option = document.createElement('option');
      option.value = province;
      option.text = province;
      provinceSelect.appendChild(option);
    });

    // 省份变化 → 更新城市(使用 optgroup)
    provinceSelect.addEventListener('change', function() {
      const province = this.value;
      citySelect.innerHTML = '';
      districtSelect.innerHTML = '<option value="">请先选择城市</option>';
      districtSelect.disabled = true;

      if (province && addressData[province]) {
        citySelect.disabled = false;

        // 默认选项
        const defaultOpt = document.createElement('option');
        defaultOpt.value = '';
        defaultOpt.text = '请选择城市';
        citySelect.appendChild(defaultOpt);

        // 使用 optgroup 按地区分组(这里省份本身就是分组)
        // 在这个简单示例中,城市直接列出
        Object.keys(addressData[province]).forEach(city => {
          const option = document.createElement('option');
          option.value = city;
          option.text = city;
          citySelect.appendChild(option);
        });
      } else {
        citySelect.disabled = true;
        const defaultOpt = document.createElement('option');
        defaultOpt.value = '';
        defaultOpt.text = '请先选择省份';
        citySelect.appendChild(defaultOpt);
      }
    });

    // 城市变化 → 更新区域
    citySelect.addEventListener('change', function() {
      const province = provinceSelect.value;
      const city = this.value;
      districtSelect.innerHTML = '';

      if (city && addressData[province] && addressData[province][city]) {
        districtSelect.disabled = false;

        const defaultOpt = document.createElement('option');
        defaultOpt.value = '';
        defaultOpt.text = '请选择区/县';
        districtSelect.appendChild(defaultOpt);

        addressData[province][city].forEach(district => {
          const option = document.createElement('option');
          option.value = district;
          option.text = district;
          districtSelect.appendChild(option);
        });
      } else {
        districtSelect.disabled = true;
        const defaultOpt = document.createElement('option');
        defaultOpt.value = '';
        defaultOpt.text = '请先选择城市';
        districtSelect.appendChild(defaultOpt);
      }
    });
  </script>
</body>
</html>

注意事项

  1. 不支持嵌套分组<optgroup> 内部只能直接包含 <option>,不能嵌套另一个 <optgroup>。如果需要多级分组,需要使用其他方案(如自定义组件)。

  2. label 属性是必需的:缺少 label<optgroup> 违反 HTML 规范,可能导致屏幕阅读器无法正确朗读分组信息。

  3. optgroup 不影响提交<optgroup> 本身不是表单控件,不会提交任何数据。提交的仍然是选中 <option>name=value 对。

  4. optgroup 内的 option disabled:即使在未禁用的 <optgroup> 中,单个 <option> 也可以设置 disabled。两者是独立的。

  5. optgroup 在 multiple 模式中同样有效<select multiple> 中也可以使用 <optgroup> 进行分组。

最佳实践

  1. 选项超过 7 个时考虑分组:当 <select> 的选项较多时,使用 <optgroup> 帮助用户快速定位。

  2. 分组标题简洁明确label 属性应简短且能准确描述分组的含义,如"前端开发"、"后端开发"。

  3. 逻辑上相关的选项归为一组:按功能、类别、地区等逻辑维度分组,而非随意分组。

  4. 配合 fieldset + legend 使用:在表单层面,<fieldset> + <legend> 标识整个选择区域的含义;在选项层面,<optgroup> + label 标识分组的含义。

  5. 通过 JS 动态控制分组禁用:根据业务逻辑(如库存、权限等),使用 JavaScript 动态设置 <optgroup>disabled 属性。

下一节

继续学习:date 日期输入

参考链接