optgroup 必须直接嵌套在 select 中且不可嵌套,仅支持单层结构;其 label 不可点击、禁用无效、样式控制受限;内部 option 可单独 disabled,屏幕阅读器会播报分组名以提升无障碍体验。

optgroup 必须直接放在 select 里面,不能嵌套在其他 optgroup 或 option 中
浏览器会忽略非法嵌套——比如把 <optgroup> 放进另一个 <optgroup> 里,或者插在 <option> 中间。解析时这些错位标签会被自动“拍平”或丢弃,导致分组失效、文字乱序甚至整个下拉变空。
正确层级只有一层: <select> → (<optgroup> 或 <option>)→ <option>。没有“二级分组”这种东西。
-
<optgroup label="前端"><optgroup label="框架"><option>Vue</option></optgroup></optgroup>—— ❌ 无效,内层<optgroup>被忽略 -
<select><option>普通项</option><optgroup label="分组"><option>组内项</option></optgroup></select>—— ✅ 合法,但注意:普通<option>和<optgroup>是同级,顺序会影响渲染位置
optgroup 的 label 不可点击、不可选中,且无法通过 CSS 修改禁用态样式
<optgroup> 本身不是选项,只是视觉分隔符。它的 label 文字默认置灰、加粗、缩进,但这是浏览器 UA 样式硬编码的,disabled 属性对它无效,:disabled 伪类也选不到它。
想自定义外观?只能靠 JS 模拟分组(比如用 <div> + <ul>),原生 <optgroup> 的样式控制粒度极低。
立即学习“前端免费学习笔记(深入)”;
-
<optgroup label="后端" disabled>—— ❌disabled属性被忽略,分组仍可见可展开 -
optgroup[label="后端"] { color: red; }—— ⚠️ 部分浏览器支持,但非标准,Chrome/Firefox 表现不一致,不建议依赖 - 真正禁用某一分组?得 JS 拦截
change事件并校验select.value是否属于该分组下的option
optgroup 内的 option 无法设置 disabled,但父 optgroup disabled 不影响子项
<optgroup disabled> 不会禁用它里面的 <option>,这点非常反直觉。浏览器只把它当视觉提示,子 <option> 依然能被选中——除非你单独给每个 <option> 加 disabled。
反过来,如果只想禁用其中几个选项,直接加 disabled 到对应 <option> 即可,<optgroup> 完全不影响。
-
<optgroup disabled><option value="py">Python</option></optgroup>—— ❌ Python 仍可选 -
<optgroup label="数据库"><option value="mysql" disabled>MySQL</option><option value="pg">PostgreSQL</option></optgroup>—— ✅ 只有 MySQL 灰掉不可选 - 注意:禁用的
<option>在表单提交时不会被发送,哪怕它在某个<optgroup>里
optgroup 在屏幕阅读器中的播报逻辑容易被忽略
主流读屏软件(NVDA、VoiceOver)会读出 <optgroup label>,并在进入该分组时播报一次,之后每读一个 <option> 会连带报出分组名,例如:“语言组,JavaScript”、“语言组,TypeScript”。这对无障碍很重要,但也意味着 label 文本必须简洁准确。
- 避免 label 写成 “? 前端框架列表 ?” 这种含符号/冗余描述,读屏时会逐字读出
- label 中文需保证语义清晰,比如用 “React 生态” 比 “相关工具” 更利于理解上下文
- 如果分组太多(>5 个),读屏用户要听完整个结构才能开始选,体验下降——这时该考虑拆页或搜索筛选,而不是堆
<optgroup>
<optgroup> 的边界很清晰:它是语义分组,不是 UI 组件,不支持嵌套、不响应交互、样式难控、无障碍行为固定。真需要多级菜单或动态分组,别硬扛,换 <details> 或 JS 下拉库更省事。



















