Stripes that stay right when rows are filtered
- active one
- archived
- active two
- archived
- active three
Caveatli:nth-child(odd of :not(.gone)) and li:not(.gone):nth-child(odd) are different selectors, and the second one is the reading most people arrive at. The first numbers within the kept rows, which is what striping wants. The second numbers among all siblings and then throws away the ones that do not match, so hiding two rows leaves two stripes in a row and it looks like a rendering bug rather than a selector you wrote.
FallbackAn engine without of S treats the whole selector as invalid and drops that rule, so the stripes disappear instead of landing on the wrong rows. A missing stripe is a much cheaper failure than a misleading one, which makes this safe to ship without a query.
/* numbers the rows that are kept */
li:nth-child(odd of :not(.gone)) {
background: Canvas;
}
/* numbers all of them, then filters:
two stripes end up side by side */
li:not(.gone):nth-child(odd) { }
Published
Questions
Does nth-child count elements with display: none?
Yes. :nth-child() counts element siblings in the DOM and knows nothing about rendering, so a row hidden with display: none still takes its number. Mark hidden rows with a class or the hidden attribute and exclude them with of :not(...).
Why doesn't nth-of-type work with a class name?
:nth-of-type() counts siblings of the same element type, so li.active:nth-of-type(odd) still numbers every <li> and then checks the class. To count only elements matching a class, use :nth-child(odd of .active).
Why do table stripes restart in each tbody?
Siblings are counted within one parent, and every <tbody> is a separate parent, so each group starts again at row 1. If you need one continuous pattern across groups, put the rows in a single <tbody>.