fieldset / legend:一条属性禁用一组控件
<fieldset> 把一组相关控件在语义与视觉上归拢成一束,<legend> 是这束控件的标题(渲染在边框上)。它最有分量的能力藏在一条布尔属性里:给 <fieldset> 写上 disabled,它的全部后代 form 控件会一起变成 disabled——不可编辑、不可聚焦、且不进表单提交。这是原生 HTML 里唯一能用一条属性批量禁用一组控件的手段。规范留了一个精心设计的例外:位于该 fieldset
第一个 <legend> 里的控件不受这层传播影响,仍可操作——因为标题区往往要放一个「整组开关」。
1 · disabled 沿 DOM 向下传播,但绕过第一个 legend
点右上角开关切换外层 <fieldset> 的 disabled 属性。禁用后,框内所有控件即刻锁死,右侧 new FormData(form) 里那些 entry 全部消失;而 legend 里那个开关复选框(它有 name=mode、且因身处第一个 legend 而不被禁用)仍留在 FormData 中。注意内层虚线 fieldset 自己没写 disabled,但外层一旦 disabled,它的后代也被连带禁用——这就是「沿 DOM 向下」的含义。
「被禁用 via ancestor fieldset」也反映在控件自身的 .disabled 上。内层那个 otp 输入框从没写过 disabled,但当外层 fieldset 被禁用时,读 otp.disabled 返回 true——规范定义的 disabled 状态是「自身有
disabled 属性,或祖先 fieldset 处于 disabled 且自己不在其第一个 legend 内」。所以脚本无需关心是谁把它禁掉的,统一读 el.disabled 即可。
2 · <fieldset> 本身是 listed element,但不可提交
<fieldset> 属于 listed 与 resettable 元素,所以它出现在 form.elements 里,也有 .type === "fieldset"、.name、.disabled 等成员;还有一个专属的 .elements 属性——收录它后代里的 listed
控件。但它不是 submittable:fieldset 自身永远不进 entry list,FormData 里不会出现名为 account 的 entry。下面把这几个属性当场读出来,并可切换 disabled 观察它是否影响这条结论。
| 禁用 / 只读手段 | 可编辑 | 可聚焦 | 进表单提交 | 典型用途 |
|---|---|---|---|---|
单个控件 disabled |
否 | 否 | 否 | 关掉某一个控件;它的值不提交。 |
<fieldset disabled> 批量 |
否(全组) | 否(全组) | 否(全组) | 一条属性禁用整组后代控件;第一个 legend 内的控件例外仍可用。 |
单个控件 readonly |
否 | 是 | 是 | 值不可改但仍随表单提交、仍可聚焦 / 复制 / 校验;仅 text 类与 <textarea> 支持。 |
3 · 同一份代码里的三件事
<form>
<fieldset name="account" disabled>
<legend>
账户设置
<!-- 第一个 legend 内的控件例外:不被禁用,仍提交 -->
<label><input type="checkbox" name="mode" value="advanced" /> 高级模式</label>
</legend>
<input name="nickname" /> <!-- disabled → 不进 FormData -->
<select name="plan">…</select> <!-- disabled → 不进 FormData -->
<fieldset>
<!-- 未写 disabled,但因外层 disabled 而被连带禁用 -->
<input name="otp" />
</fieldset>
</fieldset>
</form>
fieldset.type // "fieldset"
fieldset.disabled // true
[...fieldset.elements] // 后代 listed 控件集合
otp.disabled // true(被祖先 fieldset 传播)
new FormData(form) // 仅剩 mode=advanced;fieldset 自身不进 entry
disabled 不提交,readonly 提交——这是最常踩的混淆。被禁用的控件(无论自身写 disabled 还是被祖先 fieldset 传播)不是 successful control,不进 entry list,详见提交算法;而
readonly 的控件只是用户不能改值,提交时照样带上。要「展示但不可改、又要把值发回服务端」,用 readonly;要「这块暂时不参与、值也别发」,用 disabled。
第一个 legend 的例外,以及 fieldset 不进 entry list。传播规则只豁免第一个 <legend> 内的控件——第二个及之后的 legend(若有)不享受豁免,且只有第一个 legend 会渲染成 caption。另外别指望从 FormData 里找到 fieldset:它是 listed
但不可提交,只有它内部的 input / select / textarea 等才会贡献 entry。