By James Edwards

Contiguous Sibling Selector

By James Edwards

Every now and then I have an idea for a new CSS selector that solves a particular problem. A while ago I thought of the Regex-matching Attribute Selector, which matches attribute values according to a regular expression.

Recently, I had another idea, inspired by a project I was working on at the time—what I’ve chosen to call the Contiguous Sibling Selector.

To examine how it works, let’s begin with an HTML code example we can refer to as we go along:

    <h2>First heading</h2>
    <p>First paragraph</p>
    <p>Second paragraph</p>

    <h3>Second heading</h3>
    <p>Third paragraph</p>

CSS currently provides two sibling selectors. The first of these is the Adjacent Sibling Selector, which selects the first sibling element of a specified type (or other selection), that follows the first within the same parent context:

h2 + p { ... }

So, in our HTML example, it would select the first paragraph that immediately follows the first heading, but not the second paragraph after that, nor the third that follows the second heading.

Then there’s the General Sibling Selector, which selects all sibling elements that follow the first within the same context, and would therefore select all three paragraphs in the code example:

h2 ~ p { ... }

Between those two extremes there’s a third possibility—it would select the first two paragraphs, but not the third; in other words, contiguous siblings.

It could look like this:

h2 ~+ p { ... }

We could express that situation with this combination selector:

h2 + p, h2 + p + p { ... }

But as is often the case, that’s only ideal when the number of elements involved is small and known; what if it were much large—like a dozen or more elements? What if the number of elements were unknown, and could amount to hundreds? You could end up with selectors that make this one look compact:

h2 + p,
h2 + p + p,
h2 + p + p + p,
h2 + p + p + p + p,
h2 + p + p + p + p + p,
h2 + p + p + p + p + p + p,
h2 + p + p + p + p + p + p + p,
h2 + p + p + p + p + p + p + p + p,
h2 + p + p + p + p + p + p + p + p + p,
h2 + p + p + p + p + p + p + p + p + p + p,
h2 + p + p + p + p + p + p + p + p + p + p + p,
h2 + p + p + p + p + p + p + p + p + p + p + p + p { ... }

And it’s precisely for this situation that I came up with the idea.

I wanted a pure CSS component for a show/hide mechanism for the sections in a technical manual. The manual markup was structured as a series of level-two headings, each followed by one or more paragraphs (like the first part of the code example seen here). The paragraphs are all hidden by default, so at first all you see is the headings.

You view the content that follows each one by clicking on the heading, and that behavior is handled by scripting. But you can also come to the page with a URL containing a hash, which matches a heading ID, so that it jumps straight to that section, and I wanted that section to display automatically; for instance, to show (and only show) every paragraph that follows the specific heading.

Since each section could contain dozens of paragraphs, it seemed excessive to define that with adjacent sibling selectors—one for each possible paragraph like the bulky example above. And, of course, the general sibling selector wouldn’t do the job, because it would select all the paragraphs in all the following sections, rather than just the target section.

So, eventually, I did that with scripting too.

But I can’t help but remember how easy it would’ve been if only I’d had that contiguous sibling selector:

h2 ~+ p { display:none; }

h2:target ~+ p { display:block; }

Thumbnail credit: mortimer?

  • I think it would have been semantically more correct, as well as easier, to just wrap a div around the deeper heading and its paragraphs… like:
    <h2>First heading</h2>
    <p>First paragraph</p>
    <p>Second paragraph</p>
    <h3>Second heading</h3>
    <p>Third paragraph</p>
    It’s easier because you can use the general sibling selector that way.
    It’s semantically more correct, because each deeper heading is supposed to be a level deeper in the tree than the last.
    Don’t get me wrong, the idea is nice… but I’m not convinced by the use case.

  • @pjparra

    And what about a wrapper for each section? You could define something like this:
    .section p { display:none; }
    .section:target p { display:block; }

  • I agree with boen_robot that a div or perhaps HTML5’s section container would make sense and provide good layout options within a hierarchal document.

    That said, it’s a nice idea — let’s hope the browser vendors are watching!

  • Sergeant Rock

    On the general subject of selectors:

    I’d really rather see a Reverse Upwards Bizarro Parent Selector*, which allows one to style a container based on whether it’s got a given child or not.

    There’s the :empty selector, which is nice, but I’d really prefer something with more options

    * – name not final

    • I don’t think that would be possible without fundamentally changing how CSS works. CSS cascades, it only flows downwards, so a CSS parser only has to read the stylesheet from top to bottom – it never has to come back and re-parse earlier rules, which would be neccessary to implement a parent selector.

      • Sergeant Rock

        Hmmm – then isn’t that how the :empty selector works? Check for existence of element, go down the tree to check if it has a child, then go back up and apply styling?

      • You could be right – not sure about that actually – maybe it’s a special case because you can determine :empty by parsing a single line, since even whitespace should count as non-empty in implementations that regard whitespace as text nodes, so it doesn’t have to go deep and come back up again.

  • Yeah another wrapper would have worked, but that to my mind is superfluous markup – and a pure CSS solution is obviously better than adding HTML; HTML is not supposed to be used to solve shortcomings in CSS (though I know we all do it in practise).

    @boen_robot – I don’t buy what you’re saying there, with “each deeper heading is supposed to be a level deeper in the tree than the last.” — where did you get that from?

    • The example in the HTML 4.01 spec shows it in this way. Also, the now obsolete XHTML 2.0 “h” element also does it in this fashion. The only difference in XHTML 2 is the fact that you explicitly have a <section> element to adjust the heading level, whereas in HTML 4.01 and XHTML 1.0, according to the example, you should target certain divs to serve as section separators, and then use the appropriately numbered heading.
      I wish I could give HTML5 as an example, but in my opinion, the spec is really twisted and wicked… semantically wise. It currently uses <section> as a section separator, AND h1-h6 as different levels of headings within a section… twisted and wicked I tell you!
      Anyhow, because of the XHTML 2 way, I should probably correct myself a little – “each deeper heading is supposed to be a level or more deeper in the tree than the last.”

      • ricktheartist

        So you (noen_robot) are saying that each section (div) of a document should in theory have only one h element? I never understood that from the spec nor from any examples I have ever seen. If that was the case, why even have h1, h2, h3, etc. They could all be just “h” if that was the rule, and the div itself would define the depth.

  • @ricktheartist
    I agree completely. Indeed, why have h1-h6, when you could have a single “h” element, and make another element adjust its level automatically? That’s exactly what the XHTML 2 WG wanted to do with <section> and <h>! <div> remains a semantically neutral way to divide (or rather, group) things for which there are no better semantics (and personally, I think this is a good decision), while <section> serves explicitly for the purpose of dividing sections and subsections, and <h> is used to give the section a heading.
    I guess the reason HTML 4.01 has it in that “heavily dependant on the HTML author” way is to reduce implementation burden. Instead of forcing browsers to keep track of the section depth, they force HTML authors to do this track keeping, and browsers can just blindly render whatever heading the author has specified, even if it’s not appropriately nested.

  • Sean Hogan

    More CSS selectors could be introduced, but they would never cover all cases.

    It would be useful to have some idea what coverage is provided by the current options. I’m guessing around 95%.

    Anything added from now will be a lot of work for negligible value.

  • I’m not sure how quantifiable that is – I mean, how do you define “coverage”?

    I would define it, in these terms, as meaning “the range of different node relationships that are addressable via CSS selectors”. In those terms, coverage is probably 1-2% I reckon, because there’s a vast number of possible relationships, and the great majority of them are not directly addressable (any relationships which traverse upwards, for example).

    Personally I think there’s still significant scope for new CSS selectors. And why not? There are thousands and thousands of JavaScript objects and methods available; same with PHP; why should CSS not aspire to such comprehensiveness?

  • Sean Hogan

    I would define coverage as:

    If every possible CSS selector were available, and we looked at every page on the web, what percentage of CSS selectors in use would be satisfied by the current range available.

    • Huh? You mean, the value of a CSS selector is in how widely-useful it is?

      If so, I couldn’t agree less — the value of a CSS selector is whether it has a use *at all*; it doesn’t become less valuable simply because its use-case is marginal.

  • Dave Smith

    I too would love a pure CSS solution like given and certainly hope it inspires someone who can to implement.

    And I would like to see it as an extra stylistic string to bow to be used alongside the markup/style methods we have now and will have in the future.

    When I was thinking about this previously the solution I mustered together (that made sense to me but not necessarily based on any proper technical understanding) was h2:scope {} and h2:andScope {}.

    From the article example h2:scope {} would silently wrap p, p, h3, p and apply the styles to the silent wrap. And h2:andScope {} would silently wrap h2, p, p, h3, p and apply styles to the silent wrap.

    Reason to do it with CSS only? An extra string to bow. Adjacent and child selectors are available to use but equally we could just apply classes to elements.

    At the end of the day I don’t care how it’s done. I just want to be able to apply styles in these situations without the need to change the markup. Similar to the way it can be done in JavaScript with jQuery’s $(‘h2’).nextUntil(‘h2’);

    all the best

  • noonnope

    one question: is the scripting helping you?

  • Interesting article James.

    If the mark up is as logical as your example then perhaps you only needed to switch off the p elements that follow an h3 thus leaving you with only the paragraphs following the h2 showing.

    p {display:none}
    h2:target ~ p {display:block}
    #test h3 ~ p {display:none}

    Of course that depends on the mark up being structured exactly which is never a good assumption :)

  • Rudie Dirkx

    I think it’s a good idea. You should write the WHATWG (screw the W3C!).
    I think

    h3 ++ p { … }

    would be a nicer syntax though :)

  • bobbykjack

    That’s a suspiciously ‘tight’ document structure; did it really contain not a single table, blockquote, or list?

    That might sound facetious; what I’m really getting at is that I think the ‘contiguous sibling’ case is a very rare one, quite possibly the reason it hasn’t been considered (or accepted). As you say, there are many relationships that are not currently covered by selectors, and the need for each should surely be a factor in acceptance – or, at least, /order/ of acceptance.

    Of course, this is where something like jQuery shines; nextUntil() would give you the exact behaviour you’re looking for.

  • bobbykjack

    P.S. “Preview Comment” actually “Post[s] Comment”

Get the latest in Front-end, once a week, for free.