atanamir atanamir - 2 months ago 11x
CSS Question

Can I combine :nth-child() or :nth-of-type() with an arbitrary selector?

Is there a way to select every nth child that matches (or does not match) an arbitrary selector? For example, I want to select every odd table row, but within a subset of the rows:

table.myClass tr.row:nth-child(odd) {

<table class="myClass">
<tr class="row"> <!-- I want this -->
<tr class="row">
<tr class="row"> <!-- And this -->

just seems to count all the
elements regardless of whether or not they're of the "row" class, so I end up with the one even "row" element instead of the two I'm looking for. The same thing happens with

Can someone explain why?


This is a very common problem that arises due to a misunderstanding of how :nth-child() and :nth-of-type() work. Unfortunately, there is currently no selector-based solution as yet because Selectors does not provide a way to match the nth child that matches an arbitrary selector based on a pattern such as odd-numbered, even-numbered or any an+b where a != 1 and b != 0. This extends beyond just class selectors, to attribute selectors, negations, and more complex combinations of simple selectors.

The :nth-child() pseudo-class counts elements among all of their siblings under the same parent. It does not count only the siblings that match the rest of the selector. Similarly, the :nth-of-type() pseudo-class counts siblings sharing the same element type, which refers to the tag name in HTML, and not the rest of the selector.

This also means that if all the children of the same parent are of the same element type, for example in the case of a table body whose only children are tr elements or a list element whose only children are li elements, then :nth-child() and :nth-of-type() will behave identically, i.e. for every value of an+b, :nth-child(an+b) and :nth-of-type(an+b) will match the same set of elements.

In fact, all simple selectors in a given compound selector, including pseudo-classes such as :nth-child() and :not(), work independently of one another, rather than looking at the subset of elements that are matched by the rest of the selector.

This also implies that there is no notion of order among simple selectors within each individual compound selector1, which means for example the following two selectors are equivalent:

table.myClass tr.row:nth-child(odd)
table.myClass tr:nth-child(odd).row

Translated to English, they both mean:

Select any tr element that matches all of the following independent conditions:

  • it is an odd-numbered child of its parent;
  • it has the class "row"; and
  • it is a descendant of a table element that has the class "myClass".

(you'll notice my use of an unordered list here, just to drive the point home)

Because there is currently no pure CSS solution, you'll have to use a script to filter elements and apply styles or extra class names accordingly.

For example, the following is a common workaround using jQuery (assuming there is only one row group populated with tr elements within the table):

$('table.myClass').each(function() {
  // Note that, confusingly, jQuery's filter pseudos are 0-indexed
  // while CSS :nth-child() is 1-indexed

With the corresponding CSS:

table.myClass tr.row.odd {

Other solutions using different technologies are left as an exercise to the reader; this is just a brief, contrived example for illustration.

For what it's worth, there is a proposal for an extension to the :nth-child() notation to be added to Selectors level 4 for the specific purpose of selecting every nth child matching a given selector.2

The selector by which to filter matches is provided as an argument to :nth-child(), again due to how selectors operate independently of one another in a sequence as dictated by the existing selector syntax. So in your case, it would look like this:

table.myClass tr:nth-child(odd of .row)

(An astute reader will note right away that this ought to be :nth-child(odd of tr.row) instead, since the simple selectors tr and :nth-child() operate independently of one another as well. This is one of the problems with functional pseudo-classes that accept selectors, a can of worms I'd rather not open in the middle of this answer. Instead I'm going to go with the assumption that most sites will not have any other elements than tr elements as siblings of one another in a table body, which would make either option functionally equivalent.)

Of course, being a brand new proposal in a brand new specification, this probably won't see implementation until a few years down the road. In the meantime, you'll have to stick with using a script, as above.

1 If you specify a type or universal selector, it must come first. This does not change how selectors fundamentally work, however; it's nothing more than a syntactic quirk.

2 This was originally proposed as :nth-match(), however because it still counts an element relative only to its siblings, and not to every other element that matches the given selector, it has since as of 2014 been repurposed as an extension to the existing :nth-child() instead.