🤯 50% Off! 700+ courses, assessments, and books

Contiguous Sibling Selector

    James Edwards
    Share

    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:

    <div>
        <h2>First heading</h2>
        <p>First paragraph</p>
        <p>Second paragraph</p>
    
        <h3>Second heading</h3>
        <p>Third paragraph</p>
    </div>

    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?