Label-to-Input States

When fid­dling with in­puts & la­bels for them, I re­mem­bered one thing that amused me for a long time (Fun fact: when I started the re­search for this ar­ti­cle, I found this ques­tion at Stack­Over­flow, with a re­ally de­tailed an­swer by Bolt­Clock. The fun part is that I was the one who asked this ques­tion at this far Feb­ru­ary of the year 2012, and now to­tally for­got about it. Also, the “Even more” part of this ques­tion is still ac­tual for We­bkits/​Blinks.). Look at this HTML:

<label>
    Here is an input: 
    <input type="text" />
</label>

The fact that amuses me is that when you de­clare a state for an in­put in CSS, like with :hover or :ac­tive pseudo classes, and then you have a la­bel for that in­put, then trig­ger­ing those states over the la­bel would ac­tu­ally trig­ger the same states on the input.

Here is an ex­cerpt from the lat­est Se­lec­tors Level 4 spec:

Doc­u­ment lan­guages may de­fine ad­di­tional ways in which an el­e­ment can match :hover. For ex­am­ple, [HTML5] de­fines a la­beled con­trol el­e­ment as match­ing :hover when its la­bel is hovered.

Here is a sim­ple ex­am­ple of this be­haviour:

Hover or the input itself

If you're us­ing any mod­ern browser, you could see the in­put to be high­lighted both when you hover the in­put it­self and the la­bel as­so­ci­ated with it.

The CSS for this ex­am­ple is trivial:

input.example:hover {
  background: lime;
}

But What About Ancestors?

If you'd look at that place about the hover in the spec, you'd see the fol­low­ing sen­tence above:

An el­e­ment also matches :hover if one of its de­scen­dants in the flat tree (in­clud­ing non-el­e­ment nodes, such as text nodes) matches the above con­di­tions.

Which is a thing you could pos­si­bly al­ready know: when you hover over a de­scen­dant, then this state is also trig­gered on all the an­ces­tors (Even if the el­e­ment is out of the an­ces­tor's bounds, like be­ing po­si­tioned or moved out­side us­ing some other method, and even if the an­ces­tor would have pointer-events: none or dis­play: con­tents, it still would be marked as the one hav­ing the same state as the de­scen­dant.), which can be used in a lot of use­ful ways.

But what if we have those con­nected la­bel and in­put, and the la­bel would be placed not in one of the in­put's an­ces­tor's flat tree?

Like this:

<ul>
    <li>
        Here is
        <label class="example-label" for="Example2">a label</label>
        for the input inside another list item. Hover it!
    </li>
    <li>
        And here is the input for the above label:
        <input class="example" id="Example2" type="text" />
    </li>
</ul>

And if we'd add the same CSS as for the ex­am­ple above, we'd get the same result:

  • Here is for the input inside another list item. Hover it!
  • And here is the input for the above label:

For now the re­sult is the same. But there is one big dif­fer­ence. Let's add some styles like this:

ul.example3 > li:hover {
    box-shadow: 0 0 0 3px blue;
}

Now look at the fol­low­ing ex­am­ple and try to hover over the in­put it­self and the as­so­ci­ated label:

  • Here is for the input inside another list item. Hover it!
  • And here is the input for the above label:

You should see there that, as prob­a­bly ex­pected, only the ac­tual list item that is hov­ered would ob­tain the above styles. Giv­ing the in­put the :hover state us­ing the la­bel trick won't trig­ger the :hover over its par­ent. All like was writ­ten in the specs.

And what this also means is that as we have this be­hav­iour, the fol­low­ing se­lec­tor that could look like non­sense at the first glance would ac­tu­ally mean some­thing:

:not(:hover) > input#Example4:hover {
  background: blue;
}

If we'd take our first ex­am­ple and would just wrap an in­put into a sim­ple span:

<span><input class="example" id="Example4" type="text" /></span>

This new se­lec­tor would ac­tu­ally tar­get only (Ac­tu­ally, there is at least one other way to trig­ger it —by us­ing the de­vel­oper tools and check­ing the :hover state only for the in­put there. And if you'd find out other cases where this would work —please, re­port them to me!) the hover state of this in­put that is caused by hov­er­ing over label.

And that means that we can now ac­tu­ally style both hover states in dif­fer­ent ways!

Here is that ex­am­ple working:

Hover or the input itself

Selecting Siblings

As with a lot of other places in CSS, you could re­search a lot of things sur­round­ing this be­hav­iour. For ex­am­ple, not only the :hover state can be del­e­gated this way, but also at least the :ac­tive one.

But I'd like to men­tion one nu­ance that could be rather use­ful, but which is pre­vented by IE/​Edge not sup­port­ing it.

As we saw, the par­ent is, per spec, won't get the hover state of such del­e­gated :hover. But what if we'd want to use this :hover as a pre­req­ui­site to some­thing else? What if we would add a com­bi­na­tor af­ter it and then would try to se­lect some sib­lings that will come af­ter the input?

Like this:

  • Here is for the input inside another list item. Hover it!
  • And here is the input for the above label: , now with a link after it.

In Fire­fox and browsers based on We­bkit/​Blink you could see the link that goes af­ter the in­put to be high­lighted even when you hover the la­bel in­side an­other list item! In Edge, sadly, noth­ing hap­pens there.

And, as in the browsers that sup­port it, this be­hav­iour would work even if the in­put is dis­abled, we then could hide it us­ing the of­ten used tech­nique (us­ing clip etc.), and then use la­bels at one part of the page to high­light stuff at any other part of the page with­out any JS. What fun could we get out of it?

Breaking the Specs in the End

The most fun thing what I found when do­ing quick ex­per­i­ments is this:

  • Here is some list item with some words like Woosh and Wheee
  • And another list item!
  • And another!

Hey, look, there is a Hover it!

Sadly, there I used the for­bid­den in specs nest­ing of la­bels in­side other la­bels (which we could ac­tu­ally over­come in terms of va­lid­ity us­ing ei­ther the method for nest­ing links or by DOM ma­nip­u­la­tion), which sur­pris­ingly works as we'd ex­pect: you hover one (vi­su­ally) item, and then you get vi­sual feed­back from any num­ber of places all over the page.

Imag­ine if this would be the thing we could use with­out hacks and with­out re­ly­ing on the la­bel-to-in­put state del­e­gation.

And here we have it im­ple­mented in some way, so its en­tirely pos­si­ble to do in browsers and we'd only need the specs to sup­port it? What do you think, do we need a way to del­e­gate states from one el­e­ment to an­other in na­tive CSS? If you think that we need, tell your fel­low spec writ­ers and/​or browser ven­dors, or even come up with a draft of what it could look like in a spec by yourself!

There is a chance, that such stuff would be pos­si­ble with ex­ten­sive us­age of :has() (at the same spec) if it would be ever im­ple­mented, but yeah, only if it would be ever im­ple­mented.