fieldset 必须配 legend,否则语义失效;legend 必须为首子元素,禁用用 fieldset disabled;数据提取不依赖 fieldset 结构,radio/checkbox 分组必须用 fieldset 包裹。

fieldset 必须配 legend,否则语义失效
不加 <legend> 的 <fieldset> 在屏幕阅读器里等于不存在,部分浏览器甚至会跳过整个分组;键盘 Tab 导航也会丢失逻辑边界。这不是样式问题,是可访问性底层协议失效。
-
<legend>必须是<fieldset>的第一个子元素,不能用<div>或<p>替代 - 哪怕标题为空,也要写
<legend></legend>,不能省略 - 想视觉隐藏标题?用
visually-hidden类(position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden;),别用display: none或visibility: hidden
禁用整组控件,直接用 disabled 属性
给 <fieldset disabled> 加属性,比遍历内部所有 <input>、<select>、<button> 手动设 disabled 更可靠——它天然递归禁用所有可交互子控件,包括动态插入的元素,且表单提交时自动过滤掉这些值。
- 注意:被禁用的控件不会出现在
form.elements中,但会出现在fieldset.querySelectorAll('input')结果里,提取数据时得手动if (!el.disabled)过滤 - 禁用后,焦点无法进入该分组,键盘导航自然停在边界上,这对辅助技术用户至关重要
- 不要用 CSS 模拟禁用效果(比如灰掉 + pointer-events: none),那只是障眼法,无障碍和表单行为完全不受控
数据提取别依赖 fieldset 结构
<fieldset> 不参与表单序列化,new FormData(form) 或 form.submit() 不会按分组打包数据。想服务端拿到嵌套结构,靠的是 name 属性命名约定,不是 DOM 嵌套关系。
- 推荐用前缀式命名:
<input name="shipping[address]">、<input name="shipping[zip]">,后端能自动解析为对象 - 如果真要 JS 提取某分组内数据,得手动遍历:
fs.querySelectorAll('input[name]:not([disabled]), select[name]:not([disabled]), textarea[name]:not([disabled])') -
<legend>文本纯属描述,没 name、不传值、不能当字段标识用——别试图用它做数据分组依据
radio/checkbox 分组必须用 fieldset
一组 <input type="radio"> 若没包在同一个 <fieldset> 里,屏幕阅读器无法识别它们互斥,用户可能误以为可以多选;键盘方向键也无法在选项间切换——这违反了 ARIA Authoring Practices 的基础要求。
立即学习“前端免费学习笔记(深入)”;
- 常见错误:把 radio 拆成多个独立
<label>,中间插其他元素,导致 DOM 上不属于同一父容器 - 正确写法:
<fieldset><legend>请选择性别</legend><label><input type="radio" name="gender" value="m">男</label><label><input type="radio" name="gender" value="f">女</label></fieldset> - name 相同只是让浏览器知道“互斥”,但语义分组、可访问名称、导航边界全靠
<fieldset><legend>提供











