fieldset 和 legend 是表单可访问性的结构基石,用于为控件提供语义分组、可访问名称及批量禁用能力;缺失或误用会导致屏幕阅读器无法识别分组、键盘导航异常及后端状态丢失。

fieldset 和 legend 不是视觉装饰,而是表单可访问性的结构基石。不用它们,屏幕阅读器用户就无法理解“这堆控件属于哪一组”,键盘用户也无法用 Tab 合理跳过整组,后端还可能收不到被禁用字段的明确状态。
为什么 screen reader 会忽略没配 legend 的 fieldset
浏览器无障碍树只把 legend 文本作为该组的“可访问名称”(accessible name),前提是它必须是 fieldset 的第一个 childNode —— 注意不是 firstElementChild,而是第一个真实节点,包括换行、空格、注释都算。
- 错误写法:
<fieldset><!-- 注释 --><legend>地址</legend><input name="city"></fieldset>→ 注释是节点,legend失效 - React/Vue 中写
{showLegend && <legend>...</legend>},前面若有文本节点也会断裂语义 - 验证方式:打开 Chrome DevTools,展开 DOM 树,确认
legend是fieldset下第一个子节点 - 空
legend或仅含<svg>图标,会导致无障碍名称缺失,Lighthouse 直接报错
fieldset[disabled] 为什么有时禁用不了 input
它的作用域非常严格:只禁用 fieldset 的**直接子级**表单控件(input、select、textarea、button),不穿透任何容器元素。
- 无效写法:
<fieldset disabled><legend>支付方式</legend><div><input type="radio" name="pay"></div></fieldset>→input完全不受影响 - 正确写法:把
input提到fieldset下一级,或改用 JS 手动控制状态 -
input type="hidden"永远不响应disabled,这是规范行为,不是 bug - 别用
pointer-events: none或opacity: 0.5模拟禁用——它们不阻止键盘交互,也不排除提交值
视觉隐藏 legend 的安全做法
想隐藏文字但保留语义,必须确保它仍在 DOM 中且可被辅助技术读取。
立即学习“前端免费学习笔记(深入)”;
- 推荐 CSS:
position: absolute; clip: rect(1px, 1px, 1px, 1px);或clip-path: inset(100%); - 绝对禁止:
display: none、visibility: hidden、font-size: 0—— 这些会让legend彻底从无障碍树中消失 - 避免仅用图标(如
<svg>)替代文字;若必须加图标,应和文字共存,且文字不可省略 - 不要用
aria-label补在fieldset上替代legend—— 它无法触发原生分组行为,也不能支持disabled批量控制
嵌套 fieldset 和平级排列的区别
多步骤表单、注册页这类场景,天然适合按用户心智模型拆成多个平行 fieldset,而非嵌套。
- 推荐结构:
<form><fieldset><legend>身份信息</legend></fieldset><fieldset><legend>联系方式</legend></fieldset></form> - 嵌套写法:
<fieldset><legend>主信息</legend><fieldset><legend>紧急联系人</legend></fieldset></fieldset>→ 多层嵌套在 NVDA/JAWS 中可能跳过中间层级,上下文丢失 - 每个
fieldset必须有独立legend,不能复用或留空 - 单个控件(如一个带说明的开关)无需
fieldset,加了反而冗余
最常被忽略的点:空 fieldset(只包 p、div 或纯提示文字)会丧失语义,浏览器可能完全跳过它;fieldset 需要至少一个可聚焦控件才“被激活”,才能参与无障碍树构建。



















