← HTML 表单:从控件到提交与校验 / <label>:点文字也能操作控件 待审核 13 / 24
label · labelable

<label>:点文字也能操作控件

<label> 给一个表单控件提供可点击的文字说明。它的价值有两层:其一,把 click 与焦点转发给关联控件——点文字等于点控件本身,checkbox / radio 的命中区因此从那个小方块放大到整段文字;其二,它在无障碍树里给控件一个可访问名称(accessible name),屏幕阅读器读到控件时能念出这段文字。关联方式有显式(for 指向 id)与隐式(把控件包进 <label>)两种,而且只有名单内的labelable 元素才能被标记。本页把这两种关联、IDL 接口与 hidden 例外当场点出来。控件如何归属一张表单,见 <form> 元素与控件关联;各 type 的取值差异见 input 的 type states

1 · 两种关联:显式 for 与隐式包裹

左侧用 <label for="id"> 显式指向一个 id;右侧把 <input> 直接包进 <label>,无需任何 id。点文字(而非方块),看复选框被勾选——这正是命中区被放大的效果;底部 readout 记录每次 click 转发到了哪个控件、以及它 .checked 的变化。文本框那行点 label 文字会让它聚焦

显式与隐式的取舍。隐式包裹无需 id,结构紧凑、不怕 id 重名,适合即写即用的局部控件;但要求 label 与控件在 DOM 上是祖先 / 后代关系。显式 for 更灵活:label 与控件可以分处布局的两端(如表格中表头单元格里的 label 指向数据行的控件),只要 id 对得上即可。两者效果完全等价——都给控件一个可访问名称、都转发 click 与焦点。

2 · .labels / .control inspector:一个控件、两个 label

一个控件可以被多个 label 关联。下面给同一个 checkbox 配了两段 for 相同的 label——点任意一段都能勾选,control.labels 里会有两项。对照下方:给 <input type="hidden"> 配 label 无效,因为 hidden input 不在 labelable 名单里,它的 .labels、点击毫无反应。

labelable 元素(可被 <label> 标记) 说明
button type=submit/reset/button
input type="hidden"的全部 input——text / checkbox / radio / range / file / date…
meter 度量条 <meter>
output 计算结果 <output>
progress 进度条 <progress>
select 下拉 / 列表框 <select>
textarea 多行文本 <textarea>
custom element form-associated custom element(自定义元素声明 formAssociated 后)。
显式 for="id" 隐式包裹
写法 <label for="x">…</label> + 控件带 id="x" $<label>\dots <input>\dots </label>$,控件作后代
需要 id 需要(且要唯一) 不需要
位置 可分处 DOM 任意两端,只要 id 对上 必须是 label 的后代

若同一 <label> 上同时写了 for 又包裹了控件,for 指向的元素为准;<label> 最多关联一个 labelable 后代。

3 · 同一份代码里的三种关联

<!-- 显式:for 指向控件 id(可分处布局两端) -->
<label for="news">订阅邮件</label>
<input type="checkbox" id="news" name="news" />

<!-- 隐式:把控件包进 label,无需 for / id -->
<label>
  <input type="checkbox" name="news" />
  订阅邮件
</label>

<!-- 同一控件,多个 label:labels 收齐全部,点任一都生效 -->
<label for="agree">我同意</label>
<input type="checkbox" id="agree" />
<label for="agree">(条款与隐私政策)</label>

<!-- hidden 不在 labelable 名单:关联无效 -->
<label for="csrf">无效</label>
<input type="hidden" id="csrf" />
// IDL:控件 ↔ label 双向可读
news.labels            // NodeList,含全部关联它的 <label>
news.labels.length     // 2(被两段 label 关联)
news.labels[0].control // 指回 news(=同一控件)
news.labels[0].htmlFor // "news"

document.querySelector('input[type=hidden]').labels.length // 0

可访问性才是 <label> 的核心价值。关联后,控件在无障碍树里获得了可访问名称——屏幕阅读器聚焦该控件时会念出 label 文字(「订阅邮件,复选框,未勾选」)。没有 label 的裸控件只能念出类型,用户无从知道它代表什么。放大命中区只是顺带的好处;给每个控件一个 label,是表单可访问的基线

type="hidden" 与普通元素不可标记。labelable 名单是封闭的:把 for 指向 <input type="hidden"><div><span> 等非 labelable 元素时,关联不成立——这些元素没有 .labels,点 label 不会转发任何行为。需要给一段非控件文字做点击交互,应改用真正的控件或 role + JS,而非指望 <label>

隐式无需 id,显式 for 更灵活。两者并非优劣关系:隐式包裹省掉了 id 管理(在组件化、列表渲染里尤其省事);显式 for 则可以把 label 关联到 DOM 里别处的控件——配合 <form> 元素form 属性,label、控件、表单三者可以各在一处而仍正确联动。同一控件被多个 label 关联时,control.labels 会收齐全部,点其中任意一个都生效。