A partial archive of discourse.wicg.io as of Saturday February 24, 2024.

Proposal for enhanced sibling selector


We have the adjacent sibling selector:

E1 + E2

We have the general sibling selector:

E1 ~ E2

The general sibling selector matches all following siblings of E1 that match the selector E2. There is, however, no way to restrict the number of siblings matched.

If we want to match only the third sibling, we would write a selector like:

E1 + E2 + E3

If we want to match the first five siblings, we would write five selectors like:

E1 + E2
E1 + E2 + E3
E1 + E2 + E3 + E4
E1 + E2 + E3 + E4 + E5

What do we do if we want to match the first n siblings that match another criterion as well (e.g. only the first three h3 siblings of an h2)? What do we do if we want to stop matching sibling if some other sibling is encountered (e.g. match all siblings of h2 until another h2 is encountered).

All these are issues that I have had in my practice. I will write a proposal for a syntax of this kind of later.


It’s not custom-made CSS selector syntax, but quantity queries could get you the functionality you want today:

li:nth-last-child(6):first-child ~ li {
	color: green;

This is not what I need. Your CSS selects all child nodes of an element if their count is exactly 6.

Imagine the following markup:

  • <ol>
  •    &lt;li>Something<br>
  •    &lt;li>Something<br>
  •    &lt;li data-start>Something else<br>
  •    &lt;li>Something else<br>
  •    &lt;li>Something else<br>
  •    &lt;li data-end>Something else<br>
  •    &lt;li>Something<br>
  •    &lt;li>Something<br>
  • </ol>

In the simpler case I want selected the first three siblings of li[data-start] while in fact there may be more. Chaining the neighbor sibling selector (li[data-start], li[data-start] + li, li[data-start] + li + li, etc.) can do this but imagine the nightmare if you need more than a few.

In the more complex case I selected all items between li[data-start] and li[data-end] (inclusive or exclusive does not matter) and I don’t known in advance how many of these items there are; there may even be more than one such group among the sibling and I want them selected all.


Gotcha, I think I understand now. I think :nth-child() might be able to hack it when used with negative n.

:nth-child(-n+3) Represents the first three elements among a group of siblings.

:nth-child on MDN

But that would be very hard to match consistently and robustly.

Syntax patterned after :nth-child(), like :nth-sibling(), might be a good idea.


I propose the following selectors:

  • selector1 ~ selector2:first-sibling
    Selects only the first matching sibling element which unlike the + combinator may be non-adjacent

  • selector1 ~ selector2:last-sibling
    Selects only the last matching sibling element

  • selector1 ~ selector2:nth-sibling(an+b of selector3)
    Akin to nth-child
    nth-sibling matches siblings of selector1. A compound selector like selector1 ~ selector2:nth-child() would match all elements that are following siblings of selector1, match selector2 and are nth-child of selector1 and selector2 common parent.

  • selector1 ~ selector2:nth-last-sibling(an+b of selector3)
    Akin to nth-last-child

  • selector1 ~ selector2:start-from(n of selector3 [inclusive])
    Start matching sibling elements when the nth sibling matching selector3 is encountered. If inclusive is not specified this element is not matched even if it otherwise matches selector2.
    If “n of” is omitted, stops at the first matched selector3. If selector3 is omitted, ‘*’ assumed.

  • selector1 ~ selector2:end-at(n of selector3 [inclusive])
    Stop matching sibling elements when the nth sibling matching selector3 is encountered. If inclusive is specified this element is matched if it matches selector2 as well.
    If “n of” is omitted, stops at the first matched selector3. If selector3 is omitted, ‘*’ assumed.

  • selector1 ~ selector2:start-from(n of selector3):end-at(n of selector4)
    Naturally combines :start-from and :end-at

  • selector1 ~ selector2:not(:start-from(n of selector3):end-at(n of selector4))
    Naturally combines :start-from and :end-at and then reverses the selection.

  • selector1 ~ selector2:nth-sibling(an+b of :start-from(n of selector3):end-at(n of selector4))
    Start matching child elements when a child matching selector3 is encountered and stop when selector4 is encountered, then form groups of siblings akin to nth-child.

  • selector1 ~ selector2:first-sibling(:start-from(n of selector3):end-at(n of selector4))
    Match only the first group of sibling groups.

  • selector1 ~ selector2:last-sibling(:start-from(n of selector3):end-at(n of selector4))
    Match only the last group of sibling groups.

  • selector1 ~ selector2:only-sibling(:start-from(n of selector3):end-at(n of selector4))
    Match the group of sibling groups if it is the only such group among selector1’s following siblings

Combinators :start-from and :end-at and their combinations may be used with the child combinator as well.


  • Select only the first 20 siblings:
    E1 ~ E2:end-at(20 inclusive)

  • Select the 5th through the 10th sibling: E1 ~ E2:start-at(5 inclusive):end-at(10 inclusive)

  • Select all siblings of a H2 element until another H2 element is encountered:
    h2 ~ *:end-at(h2)
    Note the lack of the inclusive keyword here: the second h2 element itself will not be included in the matched siblings. A redundant keyword (e.g. exclusive) might be used to stress the situation.

  • Select all elements from the markup in my previous:
    ol > li:start-at([data-start]):end-at([data-end] inclusive)


selector1 ~ selector2:first-sibling is already possible with selector ~ selector2:first-of-type and similar selectors for :last-of-type, :nth-of-type, :nth-last-of-type.

As for :start-from and :end-at (and similar), what’s the use case here?