<label>
elements are notoriously hard to select with CSS in any non-trivial mark-up structure, when the state of their control is to be taken into account. Consider these frequent cases, where it is not possible to reach the label
element with CSS selectors, when the corresponding input
state is relevant:
1. <label><input> Input inside label</label>
2. <tr>
<td><input></td>
<td><label>Good ol' tables</td>
</tr>
3. <label>Label before input</label><input>
If you want to make the label react to the state of its associated control, you need to rely on JavaScript to generate classes to "listen" to. Only (1) would be addressed by a parent selector, e.g., $label input
. Reference combinators only work the other way round, to address the control from its label: label /for/ input
.
A clunky way to address a label from its input is to use :has()
:
:root:has(input#foo:invalid) label[for="foo"]
But it relies on the knowledge of the ID of the input element, if it has any. Case (1) above is then ruled out.
In many cases, the control element is partially or fully hidden, and the label acts as proxy to interact with the control, loved by designers for its independence from OS styles and ability to use ::before
and ::after
. In certain constellations handling this situation is already possible today with CSS only. Matt Mastracci played a bit with the thought of using CSS Extensions to expand on this. However, robust solutions would most probably still rely on JavaScript.
A pseudo-class :for()
can provide better control over addressing label
elements. Its content must be a single selector or selector list (mirroring :not()
), that is matched against label.control
. The specificity of :for()
is the specificity of its arguments. Examples:
label:for([type="text"]) /* label.control.type === "text" */
label:for(:invalid) /* label.control.invalid === true */
label:for(:focus) /* label.control == document.activeElement */
label:for(:disabled):for(read-only) /* chaining :for() works as expected */
If a label
has no corresponding control
, it will never match a :for()
selector.
@pspeter3 @CarlosMadeira w3c/csswg-drafts#5240