legend必须是fieldset的第一个子元素,因为屏幕阅读器依赖DOM顺序将legend文本绑定为该fieldset的可访问名称,位置错误会导致整组控件失去上下文。

fieldset 和 legend 配合不是“美化技巧”,而是语义结构 + 可访问性保障的刚性组合;删掉默认边框、改掉样式前,必须先守住可访问性底线。
为什么legend必须是fieldset的第一个子元素
这不是浏览器兼容性问题,而是无障碍树构建规则:屏幕阅读器(如 NVDA、VoiceOver)依赖 DOM 顺序把 legend 文本绑定为该 fieldset 的可访问名称。放错位置会直接导致整组控件在读屏时失去上下文。
-
<fieldset> <div><legend>地址信息</legend></div> <input name="city"> </fieldset>→legend被包裹在div内,读屏器忽略它,只读“编辑框” -
<fieldset> <input name="zip"><legend>地址信息</legend> </fieldset>→legend不是首子节点,部分旧版 Safari 会跳过播报 - React 中用 Fragment 或自定义组件包裹
legend,也会破坏 DOM 直接父子关系,等同于移除语义
禁用整个分组时,fieldset[disabled] 比手动加 disabled 更可靠
用 fieldset 的原生 disabled 属性,能递归禁用所有直属子级表单控件(input、select、textarea、button),且自动灰显、阻止焦点进入、排除在 FormData 提交之外。
- 手动给每个
input加disabled容易漏掉动态插入的控件,或忘记处理output、meter等边缘元素 -
fieldset[disabled]不影响嵌套在div或p里的控件——这是特性,不是 bug;若需禁用深层嵌套项,得靠 JS 或额外逻辑 - 被禁用的
legend文字会变灰,但它仍是可点击的(除非你加了pointer-events: none),别误以为“点了没反应=失效”
CSS 重置时最容易破坏的三个视觉线索
很多人一上来就写 fieldset { border: none; padding: 0; },结果语义还在,但用户完全看不出哪块是一组。视觉分组提示必须由你主动补上。
- 删掉
border后,至少保留outline: 2px solid #999或box-shadow: inset 0 0 0 1px #ccc,否则键盘导航时焦点框无处附着 -
legend默认有margin-bottom,清掉所有margin和padding会让标题紧贴控件顶部,失去标题感;建议只重置legend { margin: 0.5em auto 0.75em; } - 在
display: flex容器里放fieldset,legend容易偏移或被裁剪;不要对legend设display: flex,改用display: inline-flex+align-items: center
真正难的不是第一次写对,而是在后续迭代中——比如加个新控件、换套 CSS、抽离 React 组件时,始终记得检查:legend 还在不在第一位置?fieldset 还包着该包的东西吗?键盘 Tab 流是否还连贯?这些细节不靠工具检测,全靠每次改动时多看一眼 DOM 结构。

















