HTML中fieldset是唯一能将表单控件打包为逻辑组的原生方案,配合legend提供可访问名称。legend须为fieldset首个非空子元素,disabled属性仅对直属子级生效。用div模拟分组会破坏无障碍语义。
在构建包含身份证信息、工作单位、家庭成员等多组数据的复杂表单时,许多人习惯用 div 加几个边框在视觉上“分组”。但这其实是一个典型的语义陷阱——光靠画框,无法让浏览器和辅助工具识别出真正的分组逻辑。
为什么必须使用 fieldset?因为它是唯一一个能够将“控件集合”打包成逻辑组的原生 HTML 元素。配合 legend 作为组标题后,屏幕阅读器才能准确告知用户:“下面这几个输入框都属于‘紧急联系人’那一组”。
长期稳定更新的攒劲资源: >>>点此立即查看<<<
但这里有一条极易踩坑的硬性规则:legend 必须是 fieldset 内的第一个子元素,且不能为空。如果中间插入了 div、p 甚至 React Fragment,或者放在了某个 input 之后,那么 NVDA、VoiceOver 等主流读屏软件就无法将其绑定为组的可访问名称——听起来是小事,实际上语义链断裂后,整个分组的无障碍信息就全部失效了。

来看几个正反例对比:
position: absolute; clip: rect(1px, 1px, 1px, 1px),但绝不能使用 display: none 或 visibility: hidden——那等于直接把标题从无障碍树中移除。div 加 CSS 边框来模拟分组视觉上加个框,并不等于逻辑上成为“一组”。div 对屏幕阅读器完全透明,它不会告诉用户“这五个输入框都属于‘紧急联系人’”,也无法让键盘 Tab 导航感知到分组边界。更关键的是,div 没有 disabled 属性——如果你希望禁用整组控件,必须手动遍历所有子元素逐一添加 disabled,漏掉一个就可能导致用户提交了不该提交的数据。而 会原生递归禁用所有直属表单控件,并自动将其排除在 FormData 提交之外,省心很多。
fieldset 至少要有一个可聚焦控件如果只往 fieldset 里放入说明文字、div 或 p,浏览器很可能忽略其分组语义,甚至不渲染默认边框。它需要实际参与交互的元素来“激活”分组逻辑。
input、select、textarea、button、radio/checkbox 组div + CSS 更稳妥 和 disabled 状态只对直属子级生效,嵌套结构容易踩坑 的禁用继承链其实比较“脆弱”——只要中间插入一层非表单容器(比如一个 div),传播就会中断。一个常见误区是:为了布局给 input 套上 div 再塞进 fieldset,结果禁用后那个 input 依然能聚焦、能输入,提交时照常发送数据。测试方法很简单:用键盘 Tab 键逐个测试,看还有没有控件能获得焦点。
display: none——它会让整组从可访问性树中消失;应使用 inert + aria-expanded 配合 max-height 过渡来实现。最后说一个最容易忽略的暗坑:多个 fieldset 嵌套时,外层的 disabled 会穿透所有内层,但一旦某层缺失了 legend,整个禁用继承链就会断裂——不是部分失效,而是整段语义和禁用行为全部丢失。因此检查分组时,永远最先确认 legend 是否存在、是否为第一个子元素、是否有内容。这个顺序对了,后面的坑就少了一大半。