Question
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>
<td>Row
<tr class="row"> <!-- I want this -->
<td>Row
<tr class="row">
<td>Row
<tr class="row"> <!-- And this -->
<td>Row
</table>
But :nth-child()
just seems to count all the tr
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
:nth-of-type()
.
Can someone explain why?
Answer
The support of the new syntax is pretty good now so you can use:
table.myClass tr:nth-child(odd of .row)
This is a very common problem that arises due to a misunderstanding of how
:nth-child(An+B)
and :nth-of-type()
work.
In Selectors Level 3, 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 selector 1, 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)
Selectors level 4 seeks to rectify this limitation by allowing :nth- child(An+B of S)
2 to
accept an arbitrary selector argument S, again due to how selectors operate
independently of one another in a compound selector as dictated by the
existing selector syntax. So in your case, it would look like this:
table.myClass tr:nth-child(odd of .row)
You can also 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
$('tr.row:even').addClass('odd');
});
With the corresponding CSS:
table.myClass tr.row.odd {
...
}
If you're using automated testing tools such as Selenium or scraping HTML with tools like BeautifulSoup, many of these tools allow XPath as an alternative:
//table[contains(concat(' ', @class, ' '), ' myClass ')]//tr[contains(concat(' ', @class, ' '), ' row ')][position() mod 2)=1]
Other solutions using different technologies are left as an exercise to the reader; this is just a brief, contrived example for illustration.
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.