What is :nth-child(N of S) and :nth-last-child(N of S)?

Regular :nth-child() counts every element child of a parent. The Selectors Level 4 upgrade adds an optional of S clause that lets you count only the children that match a selector (or selector list). In other words: filter first, then count.

This tutorial is exclusively about these two:

  • :nth-child(N of S) — count matching children from the start.
  • :nth-last-child(N of S) — count matching children from the end.

Syntax

Think of the syntax like this:

  • :nth-child(An+B of S)
  • :nth-last-child(An+B of S)

The An+B part can be:

  • A number: 1, 2, 5
  • Keywords: odd / even
  • A formula: 2n+1, 3n, -n+4, etc.

The S part is the selector (or selector list) you want to count among. Example: .item:nth-child(2 of .item) means “the second .item among the children that are .item”.

.list > li:nth-child(odd of .hot) {
  outline: 3px solid #ef233c;
}
  
.list > li:nth-child(even of .hot) {
  outline: 3px solid #0077b6;
}
  
.list > li:nth-child(2 of .hot) {
  outline: 3px solid #111;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.wrap {
  max-width: 900px;
  padding: 18px;
  font-family: system-ui, Arial, sans-serif;
}

.list {
  display: grid;
  gap: 10px;
  padding: 0;
  margin: 0;
  list-style: none;
}

.list > li {
  border: 2px solid #111;
  border-radius: 14px;
  padding: 12px 14px;
  background: #f6f6f6;
}

.tag {
  display: inline-block;
  border: 2px solid #111;
  border-radius: 999px;
  padding: 2px 10px;
  font-size: 12px;
  background: #fff;
  margin-left: 8px;
}
  
  • Alpha .hot
  • Beta
  • Gamma .hot
  • Delta .hot
  • Epsilon
  • Zeta .hot

Browser support and progressive enhancement

The of S clause is newer than classic :nth-child(). Before using it in production, check current support on Can I use: caniuse.com/css-nth-child-of. As of writing, support sits at ~92% globally.

A practical strategy is: use it when available, and ensure your layout still looks “fine” if those styles don’t apply (progressive enhancement). You usually don’t need a complex fallback unless the styling is critical.

CSS Nth selector: the mental model that prevents headaches

Here’s the key mental model:

  1. Pick a parent.
  2. Look only at that parent’s element children (text nodes and pseudo-elements don’t count).
  3. If you used of S, filter that child list down to the children matching S.
  4. Apply the An+B math to the filtered list.

This is why :nth-child(2 of .item) still needs the element to be a child of the same parent — it can’t “jump” across containers or count items in different lists.

nth-child vs nth-last-child (with of S)

If you ever catch yourself thinking “I want the last matching one,” that’s a job for :nth-last-child(). It counts from the end of the matching set.

.list > li:nth-child(1 of .pick) {
  background: #e9f5ff;
  outline: 3px solid #0077b6;
}
  
.list > li:nth-last-child(1 of .pick) {
  background: #fff0f2;
  outline: 3px solid #ef233c;
}
  
.list > li:nth-last-child(2 of .pick) {
  background: #fff8e6;
  outline: 3px solid #111;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.wrap {
  max-width: 900px;
  padding: 18px;
  font-family: system-ui, Arial, sans-serif;
}

.list {
  display: grid;
  gap: 10px;
  padding: 0;
  margin: 0;
  list-style: none;
}

.list > li {
  border: 2px solid #111;
  border-radius: 14px;
  padding: 12px 14px;
  background: #f6f6f6;
}

.note {
  font-size: 13px;
  opacity: 0.85;
  margin-top: 10px;
}
  
  • A (.pick)
  • B
  • C (.pick)
  • D
  • E (.pick)
  • F (.pick)

CSS Nth Child Class Selector

The “class selector” use case is the crowd favorite because it fills a long-time gap in CSS: there is no :nth-of-class pseudo-class, but :nth-child(N of .class) is the practical equivalent.

CSS first child class selector

“Select the first element that has .card among siblings”:

  • Use :nth-child(1 of .card)
  • The element you’re styling must also match .card (so we include .card in the selector for clarity).
.row > .card:nth-child(1 of .card) {
  transform: translateY(-2px);
  outline: 3px solid #0077b6;
}
  
.row > .card:nth-last-child(1 of .card) {
  transform: translateY(-2px);
  outline: 3px solid #ef233c;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.wrap {
  max-width: 980px;
  padding: 18px;
  font-family: system-ui, Arial, sans-serif;
}

.row {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 12px;
}

.card,
.gap {
  border: 2px solid #111;
  border-radius: 16px;
  padding: 12px;
  background: #f6f6f6;
  min-height: 92px;
}

.card strong {
  display: block;
  font-size: 14px;
  margin-bottom: 6px;
}

.card p {
  margin: 0;
  font-size: 13px;
  opacity: 0.85;
}

.gap {
  background: repeating-linear-gradient(
    135deg,
    #f6f6f6 0 10px,
    #ffffff 10px 20px
  );
}
  
Spacer
Card A

.card

Spacer
Card B

.card

Card C

.card

Spacer

CSS second child class

“Select the second .tag among siblings” is just changing the number: :nth-child(2 of .tag).

Switch to the HTML view to see the structure.

.line > .tag:nth-child(2 of .tag) {
  background: #111;
  color: #fff;
  transform: scale(1.05);
}
  
.line > .tag:nth-child(3 of .tag) {
  background: #0077b6;
  color: #fff;
  transform: scale(1.05);
}
  
.line > .tag:nth-child(4 of .tag) {
  background: #ef233c;
  color: #fff;
  transform: scale(1.05);
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.wrap {
  max-width: 980px;
  padding: 18px;
  font-family: system-ui, Arial, sans-serif;
}

.line {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  padding: 14px;
  border: 2px solid #111;
  border-radius: 16px;
  background: #f6f6f6;
}

.tag {
  border: 2px solid #111;
  border-radius: 999px;
  padding: 6px 10px;
  background: #fff;
  font-size: 13px;
}

.sep {
  padding: 0 6px;
  opacity: 0.6;
}
  
Alpha Beta Gamma Delta

CSS last child class

“The last child with class .tag” is exactly what :nth-last-child(1 of .tag) means: count from the end of the filtered list.

.line > .tag:nth-last-child(1 of .tag) {
  outline: 3px solid #ef233c;
  background: #fff0f2;
}
  
.line > .tag:nth-last-child(2 of .tag) {
  outline: 3px solid #0077b6;
  background: #e9f5ff;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.wrap {
  max-width: 980px;
  padding: 18px;
  font-family: system-ui, Arial, sans-serif;
}

.line {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  padding: 14px;
  border: 2px solid #111;
  border-radius: 16px;
  background: #f6f6f6;
}

.tag {
  border: 2px solid #111;
  border-radius: 999px;
  padding: 6px 10px;
  background: #fff;
  font-size: 13px;
}

.mute {
  opacity: 0.6;
  font-size: 13px;
}
  
Alpha Beta separator Gamma separator Delta

Odd and even (of a class)

“Zebra stripe only the .item elements, ignoring other children” is a perfect match for odd and even with of S.

.list > .item:nth-child(odd of .item) {
  background: #ffffff;
}
  
.list > .item:nth-child(even of .item) {
  background: #e9f5ff;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.wrap {
  max-width: 900px;
  padding: 18px;
  font-family: system-ui, Arial, sans-serif;
}

.list {
  border: 2px solid #111;
  border-radius: 16px;
  overflow: hidden;
  background: #f6f6f6;
}

.item,
.ad {
  padding: 12px 14px;
  border-top: 2px solid #111;
}

.item:first-child,
.ad:first-child {
  border-top: 0;
}

.ad {
  background: #fff8e6;
  font-size: 13px;
}

.kbd {
  border: 2px solid #111;
  border-radius: 8px;
  padding: 0 6px;
  background: #fff;
  font-family: ui-monospace, SFMono-Regular, Menlo;
  font-size: 12px;
}
  
Row 1 .item
Interruption .ad (should not affect zebra)
Row 2 .item
Row 3 .item
Another ad .ad
Row 4 .item

Ranges (of a class)

Ranges are where the An+B math pays off. The classic “first 3 items” formula is -n+3. When you add of .item, it becomes “first 3 matching .item elements”.

.grid > .card:nth-child(-n + 3 of .card) {
  outline: 3px solid #0077b6;
}
  
.grid > .card:nth-child(n + 4 of .card) {
  opacity: 0.65;
}
  
.grid > .card:nth-child(n + 2 of .card):nth-child(-n + 4 of .card) {
  outline: 3px solid #111;
  background: #fff8e6;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.wrap {
  max-width: 980px;
  padding: 18px;
  font-family: system-ui, Arial, sans-serif;
}

.grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
}

.card,
.pad {
  border: 2px solid #111;
  border-radius: 18px;
  background: #f6f6f6;
  padding: 12px;
  min-height: 110px;
}

.card strong {
  display: block;
  margin-bottom: 6px;
  font-size: 14px;
}

.card p {
  margin: 0;
  font-size: 13px;
  opacity: 0.85;
}

.pad {
  background: repeating-linear-gradient(
    135deg,
    #f6f6f6 0 10px,
    #ffffff 10px 20px
  );
}
  
Spacer
Card 1

.card

Card 2

.card

Spacer
Card 3

.card

Card 4

.card

Card 5

.card

Spacer

The “inner range” works by chaining two filtered :nth-child() tests together. :nth-child(n + 2 of .card) means “the 2nd .card and onward”, and :nth-child(-n + 4 of .card) means “up to the 4th .card”. When both must be true at the same time, the overlap is only cards 2 through 4.

CSS Nth-child attributes

Attribute selectors are just selectors, which means they work perfectly inside of S. This is great when your HTML uses data attributes like data-state, data-featured, etc.

CSS first child with attribute

“First element that has [data-featured]”: :nth-child(1 of [data-featured]).

.list > .row:nth-child(1 of [data-featured]) {
  outline: 3px solid #0077b6;
  background: #e9f5ff;
}
  
.list > .row:nth-child(2 of [data-featured]) {
  outline: 3px solid #111;
  background: #fff8e6;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.wrap {
  max-width: 900px;
  padding: 18px;
  font-family: system-ui, Arial, sans-serif;
}

.list {
  display: grid;
  gap: 10px;
}

.row {
  border: 2px solid #111;
  border-radius: 14px;
  padding: 12px 14px;
  background: #f6f6f6;
  display: flex;
  gap: 10px;
  align-items: baseline;
  justify-content: space-between;
}

.badge {
  border: 2px solid #111;
  border-radius: 999px;
  padding: 2px 10px;
  background: #fff;
  font-size: 12px;
}

.muted {
  opacity: 0.65;
  font-size: 12px;
}
  
Alpha no attribute
Beta [data-featured]
Gamma no attribute
Delta [data-featured]
Epsilon [data-featured]

CSS last child with attribute

“Last element with [data-featured]”: :nth-last-child(1 of [data-featured]).

.list > .row:nth-last-child(1 of [data-featured]) {
  outline: 3px solid #ef233c;
  background: #fff0f2;
}
  
.list > .row:nth-last-child(2 of [data-featured]) {
  outline: 3px solid #0077b6;
  background: #e9f5ff;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.wrap {
  max-width: 900px;
  padding: 18px;
  font-family: system-ui, Arial, sans-serif;
}

.list {
  display: grid;
  gap: 10px;
}

.row {
  border: 2px solid #111;
  border-radius: 14px;
  padding: 12px 14px;
  background: #f6f6f6;
  display: flex;
  gap: 10px;
  align-items: baseline;
  justify-content: space-between;
}

.badge {
  border: 2px solid #111;
  border-radius: 999px;
  padding: 2px 10px;
  background: #fff;
  font-size: 12px;
}

.muted {
  opacity: 0.65;
  font-size: 12px;
}
  
Alpha [data-featured]
Beta no attribute
Gamma [data-featured]
Delta no attribute
Epsilon [data-featured]

Combined selectors: class + attribute

You can combine selectors inside of S like you normally would. This is a very realistic pattern when you have “cards”, but only some are “featured”, and only some are “available”.

.grid > .card:nth-child(1 of .card[data-state="open"]) {
  outline: 3px solid #0077b6;
}
  
.grid > .card:nth-child(2 of .card[data-state="open"]) {
  outline: 3px solid #111;
}
  
.grid > .card:nth-last-child(1 of .card[data-featured]) {
  outline: 3px solid #ef233c;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.wrap {
  max-width: 980px;
  padding: 18px;
  font-family: system-ui, Arial, sans-serif;
}

.grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
}

.card,
.other {
  border: 2px solid #111;
  border-radius: 18px;
  background: #f6f6f6;
  padding: 12px;
  min-height: 120px;
}

.card strong {
  display: block;
  margin-bottom: 6px;
  font-size: 14px;
}

.card p {
  margin: 0;
  font-size: 12px;
  opacity: 0.8;
}

.pill {
  display: inline-block;
  border: 2px solid #111;
  border-radius: 999px;
  padding: 2px 10px;
  background: #fff;
  font-size: 12px;
  margin-top: 10px;
}
  
Not a card
Card A

data-state="open"

.card
Card B

data-state="closed"

.card
Card C

open + featured

.card
Another non-card
Card D

featured

.card

Learn more about attribute selectors in the CSS Attribute Selector Interactive Tutorial.

Selector lists in “of S”

The S in of S can be a comma-separated selector list, meaning you can count “children that match A or B”.

Example: “count only visible things”, where “visible” means either .is-visible or [data-visible].

.list > .row:nth-child(odd of .is-visible, [data-visible]) {
  background: #e9f5ff;
}
  
.list > .row:nth-child(even of .is-visible, [data-visible]) {
  background: #ffffff;
}
  
.list > .row:nth-last-child(1 of .is-visible, [data-visible]) {
  outline: 3px solid #ef233c;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.wrap {
  max-width: 900px;
  padding: 18px;
  font-family: system-ui, Arial, sans-serif;
}

.list {
  border: 2px solid #111;
  border-radius: 16px;
  overflow: hidden;
  background: #f6f6f6;
}

.row {
  padding: 12px 14px;
  border-top: 2px solid #111;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.row:first-child {
  border-top: 0;
}

.badge {
  border: 2px solid #111;
  border-radius: 999px;
  padding: 2px 10px;
  background: #fff;
  font-size: 12px;
}

.dim {
  opacity: 0.65;
}
  
Alpha .is-visible
Beta not visible
Gamma [data-visible]
Delta not visible
Epsilon .is-visible

Practical recipes with :nth-child(N of S)

Want “every 3rd featured card” but your grid also contains ads, placeholders, or other elements? Filter down to just featured cards, then use 3n.

.grid > .card:nth-child(3n of .card[data-featured]) {
  outline: 3px solid #0077b6;
  transform: translateY(-2px);
}
  
.grid > .card:nth-child(3n + 2 of .card[data-featured]) {
  outline: 3px solid #ef233c;
  transform: translateY(-2px);
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.wrap {
  max-width: 980px;
  padding: 18px;
  font-family: system-ui, Arial, sans-serif;
}

.grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
}

.card,
.ad {
  border: 2px solid #111;
  border-radius: 18px;
  background: #f6f6f6;
  overflow: hidden;
}

.card .media {
  aspect-ratio: 16 / 10;
  background: #fff;
}

.card img {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
}

.card .body {
  padding: 12px;
}

.card strong {
  display: block;
  margin-bottom: 6px;
  font-size: 14px;
}

.card p {
  margin: 0;
  font-size: 12px;
  opacity: 0.8;
}

.ad {
  padding: 12px;
  background: #fff8e6;
  font-size: 13px;
}
  
Random
Card A

featured

Ad / not a featured card
Random
Card B

featured

Random
Card C

not featured

Random
Card D

featured

Random
Card E

featured

Another non-featured thing
Random
Card F

featured

Why :nth-child(N of S) might not work

1) Browser support: the selector is ignored

If the browser doesn’t support the of S clause yet, the whole selector won’t match. Always check current support here: caniuse.com/css-nth-child-of.

2) Wrong parent / wrong siblings

:nth-child() and :nth-last-child() only reason about siblings under the same parent. If you’re expecting it to count items across multiple wrappers, it won’t.

  • Fix: move items under a shared parent, or target the correct parent in your selector.

3) You filtered everything out

If no child matches S, then there is nothing to count, so nothing matches. Common causes:

  • Typo in the class or attribute.
  • You’re using [data-flag] but your HTML has data-flag="true" and you actually meant [data-flag="true"].
  • You used a selector that can’t match the children you’re targeting (example: of button but your children are a tags).

4) Specificity changes (and another rule wins)

Adding of S can change the “strength” of your selector because it includes the specificity of S. If your rule seems ignored, it might be losing in the cascade to a more specific selector.

  • Fix: make your selector slightly more specific (carefully), reorder CSS, or use cascade layers.

Quick debug checklist

  • Did I paste this into a browser that supports of S?
  • Is the element a direct child of the parent I think it is?
  • Do at least N children match S?
  • Am I targeting the element I mean, or just “some descendant”?
  • Is another selector overriding my styles?

Quick Cheat Sheet

This section is a rapid-fire reference for :nth-child(N of S) and :nth-last-child(N of S). Think: filter by S, then count.

Basic form

  • :nth-child(N of S) selects the Nth matching child from the start.
  • :nth-last-child(N of S) selects the Nth matching child from the end.
  • N can be a number, odd, even, or an An+B formula.
  • S can be a selector or a selector list (comma-separated).

Class selectors

  • First .item among siblings: .item:nth-child(1 of .item)
  • Second .item: .item:nth-child(2 of .item)
  • Last .item: .item:nth-last-child(1 of .item)
  • Second-to-last .item: .item:nth-last-child(2 of .item)
  • Odd .item only (zebra, ignoring other children): .item:nth-child(odd of .item)
  • Even .item only: .item:nth-child(even of .item)

Attribute selectors

  • First [data-featured]: :nth-child(1 of [data-featured])
  • Last [data-featured]: :nth-last-child(1 of [data-featured])
  • First [data-state="open"]: :nth-child(1 of [data-state="open"])
  • Every third [data-featured]: :nth-child(3n of [data-featured])

Combined selectors (class + attribute + element)

  • First open card: .card:nth-child(1 of .card[data-state="open"])
  • Last featured card: .card:nth-last-child(1 of .card[data-featured])
  • Second open button: button:nth-child(2 of button[data-state="open"])

Ranges (the “first few”, “everything after”, etc.)

  • First 3 matching .card: .card:nth-child(-n + 3 of .card)
  • From the 4th matching .card onward: .card:nth-child(n + 4 of .card)
  • Only matches 2 through 5 (inclusive): .item:nth-child(n + 2 of .item):nth-child(-n + 5 of .item)
  • Last 2 matching items: .item:nth-last-child(-n + 2 of .item)

Selector lists (match A or B)

  • Odd “visible” rows where visible means .is-visible or [data-visible]: .row:nth-child(odd of .is-visible, [data-visible])
  • Last matching item from a list of two selectors: :nth-last-child(1 of .primary, .secondary)

CSS Nth Child(N of Selector) Conclusion

:nth-child(N of S) and :nth-last-child(N of S) are basically “nth-of-type, but for anything”: you can count classes, attributes, and even combined selectors without changing your HTML. That makes them perfect for modern component styling — just keep an eye on support and use them as progressive enhancement.

Just starting with :nth-child? Check out the CSS Nth Child Interactive Tutorial.