Why does the general-sibling combinator allow toggling pseudo-element's content, but not the adjacent-sibling?
Asked Answered
U

2

10

In this question "CSS3 Selector That Works like jQuery's .click()?" I posted an answer using the :checked state of an input, of type="checkbox" to toggle the display of an element.

This is the HTML of the demo I posted in that answer:

<input type="checkbox" id="switch" />
<nav>
    <h2>This would be the 'navigation' element.</h2>
</nav>
<label for="switch">Toggle navigation</label>

And the CSS (with transitions stripped for brevity):

#switch {
    display: none;
}
#switch + nav {
    height: 0;
    overflow: hidden;
    /* transitions followed */
}
#switch:checked + nav {
    height: 4em;
    color: #000;
    background-color: #ffa;
    /* transitions followed */
}

label {
    cursor: pointer;
}

JS Fiddle demo.

Once I'd posted the answer it occurred to me that we could also toggle the text of the label used to trigger the state-change of that checkbox, using the following selectors (having amended the label's text to 'navigation'):

label {
    display: inline-block;
    cursor: pointer;
}

#switch + nav + label::before {
    content: 'Show ';
}

#switch:checked + nav + label::before {
    content: 'Hide ';
}

Simplified/basic JS Fiddle demo.

This did not work, in that while the selector matched while the input was in its unchecked state (and the label showed Show navigation), the selector failed to match when the state of the input changed. Note that the transitions were still effected on the nav element, and the original matching selector indicates that the next-sibling combinator matched originally. The above link shows a simplified demo of the not-working (in Chrome 27/Windows XP) selectors.

It then occurred to me to try the general-sibling combinator, to reduce the selector-chain. which resulted in the following CSS (with transitions again stripped for brevity):

#switch:checked + nav {
    background-color: #ffa;
}

label {
    display: inline-block;
    cursor: pointer;
}

#switch ~ label::before {
    content: 'Show ';
}

#switch:checked ~ label::before {
    content: 'Hide ';
}

JS Fiddle demo.

Somewhat to my surprise, this worked (the content of the label changed in response to the changed-state of the input).

So, the question: why does the general-sibling combinator allow for updating of a later-sibling while chained next-sibling combinators (which match the elements and the structure of the DOM) does not?

Further, this does seem to work in Firefox (21, on Windows XP); so I guess the question is altered slightly to include: is this a bug in Chrome/Webkit, or an expected behaviour?

And, even further, it seems that while this is a bug in Chrome (thanks @Boltclock), there's a somewhat ludicrous 'do-nothing' animation that fixes the non-working demo (though other, perhaps better, alternatives exist, as Scott's answer shows):

body {
    -webkit-animation: bugfix infinite 1s;
}
@-webkit-keyframes bugfix {
    from {
        padding: 0;
    }
    to {
        padding: 0;
    }
}
#switch {
}
#switch + nav {
    -moz-transition: all 1s linear;
    -ms-transition: all 1s linear;
    -o-transition: all 1s linear;
    -webkit-transition: all 1s linear;
    transition: all 1s linear;
}
#switch:checked + nav {
    background-color: #ffa;
    -moz-transition: all 1s linear;
    -ms-transition: all 1s linear;
    -o-transition: all 1s linear;
    -webkit-transition: all 1s linear;
    transition: all 1s linear;
}
label {
    display: inline-block;
    cursor: pointer;
}
#switch + nav + label::before {
    content:'Show ';
}
#switch:checked + nav + label::before {
    content:'Hide ';
}

JS Fiddle demo.

Note: the reason I'm updating the question with this 'fix,' rather than posting it as an answer, is simply because the question wasn't "how can I fix this?" but (basically) "why doesn't it work?"

Unmeasured answered 20/6, 2013 at 16:48 Comment(0)
D
9

This is a long-standing bug in WebKit browsers related to the use of certain dynamic pseudo-classes with next-sibling combinators. This happens whether you're applying styles to the sibling element itself or a pseudo-element of that sibling element.

I don't know if anybody has filed a bug report yet, but this has been seen rather frequently on the site:

Strangely it was also reported that Chrome had issues with the general sibling combinator, but as you note it works in your given scenario:

So either that was fixed, or something else triggers/triggered it.

Dominations answered 20/6, 2013 at 17:6 Comment(6)
Ugh, I need to learn how to effectively-search for Webkit bugs.Unmeasured
Don't worry, they're hard to find on purpose. You'll just have to encounter them as you work.Dominations
Thanks! And, suddenly, I don't know whether to be happy that Opera's switching to Webkit (they might fix the bug) or wary (they might adopt new, and frustrating, old-bugs).Unmeasured
We're just going to have to trust the good people at Opera. Presto was an awesome engine that did many things way better than WebKit, so either they manage to patch things up, or we're basically hosed.Dominations
Which is the reason I was, even initially, wary of the change. While I didn't love Opera, as such, I did admire their rendering engine, and their implementation of standards. Oh, and ludicrously, using a 'do-nothing' animation fixes it.Unmeasured
You should know that I am extremely biased against WebKit and toward Trident and Gecko at this point :P I especially applaud Microsoft's strategy of focusing IE8 on CSS2.1 compliance — it really shows, and puts sketchy tech demos like Chrome in their place.Dominations
B
16

Bug Work Around

Apparently, certain valid pseudo-classes chained together allows it to work.

These work (see Fiddle #1, Fiddle #2, Fiddle #3):

#switch:checked + nav:only-of-type + label::before
#switch:checked + nav:nth-of-type(1) + label::before
#switch:checked + nav:nth-child(2) + label::before

This did not (see Fiddle #4):

#switch:checked + nav:not([class]) + label::before

I tried some other :not() combinations, none of which allowed it to work.

** Best choice **

#switch:checked + nav:nth-child(n) + label::before
Behre answered 20/6, 2013 at 17:34 Comment(7)
Does :nth-child(n) work? If so, that's the best bet, because it's a guaranteed match (well, technically equivalent to :not(:root), but the root element is irrelevant here).Dominations
Your answer reminds me of this.Dominations
YES! I was seeking a "guaranteed" match idea, and that does work!Behre
@Scott: I'm up-voting because that really is quite awesome! But I'll leave the accepted answer as-is, because the work-around wasn't the question as such (for another work-around see the question itself, for example); but seriously: way to go! =)Unmeasured
Yes, I did not expect you to switch your accepted answer, as it is in fact a bug. But I did feel this would be useful for others.Behre
@BoltClock: thanks. And David, I know you added the "do-nothing" animation fix to your question, but personally, I would minimize or eliminate that since I think this workaround is better than that one.Behre
I tend to agree, but I had no intention that it should be the solution, it was just the option I found while searching for solutions to the problem (though I'll edit in a moment to add that it's merely one such solution).Unmeasured
D
9

This is a long-standing bug in WebKit browsers related to the use of certain dynamic pseudo-classes with next-sibling combinators. This happens whether you're applying styles to the sibling element itself or a pseudo-element of that sibling element.

I don't know if anybody has filed a bug report yet, but this has been seen rather frequently on the site:

Strangely it was also reported that Chrome had issues with the general sibling combinator, but as you note it works in your given scenario:

So either that was fixed, or something else triggers/triggered it.

Dominations answered 20/6, 2013 at 17:6 Comment(6)
Ugh, I need to learn how to effectively-search for Webkit bugs.Unmeasured
Don't worry, they're hard to find on purpose. You'll just have to encounter them as you work.Dominations
Thanks! And, suddenly, I don't know whether to be happy that Opera's switching to Webkit (they might fix the bug) or wary (they might adopt new, and frustrating, old-bugs).Unmeasured
We're just going to have to trust the good people at Opera. Presto was an awesome engine that did many things way better than WebKit, so either they manage to patch things up, or we're basically hosed.Dominations
Which is the reason I was, even initially, wary of the change. While I didn't love Opera, as such, I did admire their rendering engine, and their implementation of standards. Oh, and ludicrously, using a 'do-nothing' animation fixes it.Unmeasured
You should know that I am extremely biased against WebKit and toward Trident and Gecko at this point :P I especially applaud Microsoft's strategy of focusing IE8 on CSS2.1 compliance — it really shows, and puts sketchy tech demos like Chrome in their place.Dominations

© 2022 - 2024 — McMap. All rights reserved.