In a perfect world, CSS might have a magical :nth-of-class() selector that lets you target “the 2nd element with class .class”. In the real world, CSS does not have a direct :nth-of-class pseudo-class.

But don’t close the tab yet — CSS Selectors Level 4 quietly gave us the next best thing: the selector list argument for :nth-child() and :nth-last-child(). This is the equivalent of “nth-of-class”, except it works with any selector list, not just a class.

It looks like this:

  • :nth-child(N of .class) — “the Nth matching child from the start”
  • :nth-last-child(N of .class) — “the Nth matching child from the end”

Browser support is around 92.2% at the time of writing. You can check the latest support data here: caniuse.com/css-nth-child-of.

In this tutorial, we’ll use :nth-child(N of .class) and :nth-last-child(N of .class) to build “nth-of-class” behavior, step by step, with interactive playgrounds.

What Is :nth-child(… of S)?

The classic :nth-child() counts all siblings (all children of the same parent) and selects the one(s) that match a pattern. The upgraded version adds an optional of S clause (where S is a selector list). That clause tells the browser: “Only count the children that match this selector list.”

Example: :nth-child(1 of .class) means “the first child among the children that have class .class”. Any non-.class siblings are ignored for the counting step.

.row > :nth-child(1 of .tag) {
  outline: 4px solid #111;
}
  
.row > .tag:nth-child(1) {
  outline: 4px solid #111;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

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

.row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  padding: 14px;
  border: 3px solid #111;
  border-radius: 16px;
  background: #f6f6f6;
}

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

.tag {
  background: #eaf4ff;
}

.note {
  background: #fff3d6;
}

.hint {
  margin-top: 10px;
  font-size: 14px;
  line-height: 1.4;
}
  
.note (not .tag) .tag #1 .note (not .tag) .tag #2 .tag #3 .note (not .tag)

Snippet 1 uses :nth-child(1 of .tag) → outlines the first .tag.

Snippet 2 uses .tag:nth-child(1) → only matches if the .tag is literally the first child.

CSS “First Of Class” With :nth-child(1 of .class)

The most common “nth-of-class” request is: “Select the first element with class .item among its siblings.”

That’s exactly: :nth-child(1 of .item)

.list > :nth-child(1 of .item) {
  background: #111;
  color: #fff;
  transform: translateY(-2px);
}
  
.list > :nth-child(2 of .item) {
  background: #111;
  color: #fff;
  transform: translateY(-2px);
}
  
.list > :nth-child(3 of .item) {
  background: #111;
  color: #fff;
  transform: translateY(-2px);
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

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

.list {
  display: grid;
  gap: 10px;
  border: 3px solid #111;
  border-radius: 16px;
  padding: 14px;
  background: #f6f6f6;
}

.card {
  padding: 12px 14px;
  border-radius: 14px;
  border: 2px solid #111;
  background: #fff;
  transition: transform 200ms ease;
}

.ad {
  border-style: dashed;
  background: #fff3d6;
}

.kicker {
  font-size: 13px;
  opacity: 0.8;
}
  
Not an item
.ad
Item
.item #1
Not an item
.ad
Item
.item #2
Item
.item #3

CSS “Last Of Class” With :nth-last-child(1 of .class)

To select the last matching class among siblings, use :nth-last-child() with the same of clause.

  • :nth-last-child(1 of .item) → last .item
  • :nth-last-child(2 of .item) → second-to-last .item
.list > :nth-last-child(1 of .item) {
  background: #111;
  color: #fff;
}
  
.list > :nth-last-child(2 of .item) {
  background: #111;
  color: #fff;
}
  
.list > :nth-last-child(3 of .item) {
  background: #111;
  color: #fff;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

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

.list {
  display: grid;
  gap: 10px;
  border: 3px solid #111;
  border-radius: 16px;
  padding: 14px;
  background: #f6f6f6;
}

.card {
  padding: 12px 14px;
  border-radius: 14px;
  border: 2px solid #111;
  background: #fff;
}

.ad {
  border-style: dashed;
  background: #fff3d6;
}

.kicker {
  font-size: 13px;
  opacity: 0.8;
}
  
Item
.item #1
Not an item
.ad
Item
.item #2
Item
.item #3
Not an item
.ad
Item
.item #4

Odd And Even “Of Class”

You can use odd and even as the N part. The key is that the odd/even counting happens on the filtered set (the elements matching the selector list).

.grid > :nth-child(odd of .tile) {
  background: #111;
  color: #fff;
}
  
.grid > :nth-child(even of .tile) {
  background: #111;
  color: #fff;
}
  
*,
::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(3, minmax(0, 1fr));
  gap: 10px;
  border: 3px solid #111;
  border-radius: 16px;
  padding: 14px;
  background: #f6f6f6;
}

.tile {
  padding: 16px 12px;
  border-radius: 14px;
  border: 2px solid #111;
  background: #fff;
  text-align: center;
}

.gap {
  padding: 16px 12px;
  border-radius: 14px;
  border: 2px dashed #111;
  background: #fff3d6;
  text-align: center;
  font-size: 14px;
}
  
.tile #1
not .tile
.tile #2
.tile #3
not .tile
.tile #4
.tile #5
.tile #6
not .tile

The Real Power: An+B “Of Class”

When you write An+B, you’re describing a repeating pattern:

  • A is the step size
  • B is the starting offset
  • n is 0, 1, 2, 3…

Example:

  • 3n → 3, 6, 9, 12…
  • 3n+1 → 1, 4, 7, 10…
  • 4n-1 → 3, 7, 11, 15…

With the of .class clause, those sequences apply to “the filtered set”.

.row > :nth-child(3n of .badge) {
  background: #111;
  color: #fff;
}
  
.row > :nth-child(3n + 1 of .badge) {
  background: #111;
  color: #fff;
}
  
.row > :nth-child(4n - 1 of .badge) {
  background: #111;
  color: #fff;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

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

.row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  padding: 14px;
  border: 3px solid #111;
  border-radius: 16px;
  background: #f6f6f6;
}

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

.badge {
  background: #eaf4ff;
}

.spacer {
  background: #fff3d6;
  border-style: dashed;
}
  
.badge #1 not .badge .badge #2 .badge #3 not .badge .badge #4 .badge #5 .badge #6 not .badge .badge #7

Ranges “Of Class”

Ranges are where this feature becomes ridiculously useful. You can select “the first 3 of class”, “2 through 5 of class”, and more, using the same math you’d use with regular :nth-child().

Selecting The First N Of A Class

-n + 3 means “1, 2, 3”. So :nth-child(-n + 3 of .item) selects the first three matching .item elements.

:nth-child(n + 4 of .item) selects all .item elements starting from the fourth one.

.stack > :nth-child(-n + 3 of .item) {
  background: #111;
  color: #fff;
}
  
.stack > :nth-child(n + 4 of .item) {
  background: #111;
  color: #fff;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

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

.stack {
  display: grid;
  gap: 10px;
  border: 3px solid #111;
  border-radius: 16px;
  padding: 14px;
  background: #f6f6f6;
}

.item,
.other {
  padding: 12px 14px;
  border-radius: 14px;
  border: 2px solid #111;
  background: #fff;
}

.other {
  border-style: dashed;
  background: #fff3d6;
}

.kicker {
  font-size: 13px;
  opacity: 0.8;
}
  
Not counted
.other
Counted
.item #1
Counted
.item #2
Not counted
.other
Counted
.item #3
Counted
.item #4
Counted
.item #5

Selecting A Middle Range Of A Class

Want “items 2 through 4” (within the filtered set)? Combine two conditions:

  • n + 2 → 2, 3, 4, 5…
  • -n + 4 → 1, 2, 3, 4

Put together: :nth-child(n + 2 of .item):nth-child(-n + 4 of .item)

.stack > :nth-child(n + 2 of .item):nth-child(-n + 4 of .item) {
  background: #111;
  color: #fff;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

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

.stack {
  display: grid;
  gap: 10px;
  border: 3px solid #111;
  border-radius: 16px;
  padding: 14px;
  background: #f6f6f6;
}

.item,
.other {
  padding: 12px 14px;
  border-radius: 14px;
  border: 2px solid #111;
  background: #fff;
}

.other {
  border-style: dashed;
  background: #fff3d6;
}

.kicker {
  font-size: 13px;
  opacity: 0.8;
}
  
Counted
.item #1
Not counted
.other
Counted
.item #2
Counted
.item #3
Counted
.item #4
Not counted
.other
Counted
.item #5

Not Just A Class: Selector Lists

The of S part accepts a selector list — meaning you can use commas, combine classes, or even use more complex selectors. This is why it’s “nth-of-anything”, not just “nth-of-class”.

Example: Count .chip And .tag Together

.row > :nth-child(2 of .chip, .tag) {
  background: #111;
  color: #fff;
}
  
.row > :nth-child(3 of .chip, .tag) {
  background: #111;
  color: #fff;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

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

.row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  padding: 14px;
  border: 3px solid #111;
  border-radius: 16px;
  background: #f6f6f6;
}

.chip,
.tag,
.note {
  padding: 8px 10px;
  border-radius: 999px;
  border: 2px solid #111;
  background: #fff;
  font-size: 14px;
}

.chip {
  background: #eaf4ff;
}

.tag {
  background: #e7ffe8;
}

.note {
  background: #fff3d6;
  border-style: dashed;
}
  
.note (ignored) .chip #1 .tag #2 .note (ignored) .chip #3 .tag #4

Example: “Of .item.featured

You can filter to a more specific subset, like “items that are both .item and .featured”.

.list > :nth-child(1 of .item.featured) {
  background: #111;
  color: #fff;
}
  
.list > :nth-child(2 of .item.featured) {
  background: #111;
  color: #fff;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

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

.list {
  display: grid;
  gap: 10px;
  border: 3px solid #111;
  border-radius: 16px;
  padding: 14px;
  background: #f6f6f6;
}

.item {
  padding: 12px 14px;
  border-radius: 14px;
  border: 2px solid #111;
  background: #fff;
}

.featured {
  box-shadow: 0 0 0 3px #111 inset;
}

.kicker {
  font-size: 13px;
  opacity: 0.8;
}
  
Not featured
.item
Not featured
.item

Patterns From The End With :nth-last-child(… of .class)

Everything you learned about patterns and ranges works from the end too. This is great for things like: “highlight the last 2 tags”, “hide all but the last 3 chips”, or “style the second-to-last warning”.

.row > :nth-last-child(-n + 2 of .tag) {
  background: #111;
  color: #fff;
}
  
.row > :nth-last-child(2 of .tag) {
  background: #111;
  color: #fff;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

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

.row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  padding: 14px;
  border: 3px solid #111;
  border-radius: 16px;
  background: #f6f6f6;
}

.tag,
.other {
  padding: 8px 10px;
  border-radius: 999px;
  border: 2px solid #111;
  background: #fff;
  font-size: 14px;
}

.tag {
  background: #eaf4ff;
}

.other {
  background: #fff3d6;
  border-style: dashed;
}
  
.tag #1 not .tag .tag #2 .tag #3 not .tag .tag #4 .tag #5

Why “Nth Of Class” Might Not Work

When :nth-child(N of .class) “does nothing”, it’s almost always one of these:

  • You’re not selecting the direct children. :nth-child() always works on siblings (children of the same parent). If you meant “any descendant”, you’ll need a different structure or selector.
  • Your selector is missing the parent relationship. Example: .list :nth-child(1 of .item) can match lots of nested stuff. Often you want .list > :nth-child(1 of .item) to only count direct children. Learn more in the CSS Direct Child Selector (>) Interactive Tutorial.
  • You used the old pattern by accident: .item:nth-child(2) is not “2nd item”, it’s “an item that is also the 2nd child”. The of clause is the crucial part.
  • Browser support. If you’re testing in a browser that doesn’t support the of clause, the whole selector is invalid and won’t match. Check: caniuse.com/css-nth-child-of.
  • Your “filtered set” has fewer matches than you think. If there is only one .item, then :nth-child(2 of .item) can’t match anything.

A Quick Debug Playground

This playground shows a classic mistake and the fix.

.panel :nth-child(2 of .box) {
  outline: 4px solid #111;
}
  
.panel > .row > :nth-child(2 of .box) {
  outline: 4px solid #111;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

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

.panel {
  border: 3px solid #111;
  border-radius: 16px;
  padding: 14px;
  background: #f6f6f6;
  display: grid;
  gap: 10px;
}

.row {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.box {
  width: 120px;
  height: 70px;
  border: 2px solid #111;
  border-radius: 14px;
  display: grid;
  place-items: center;
  background: #eaf4ff;
}

.other {
  width: 120px;
  height: 70px;
  border: 2px dashed #111;
  border-radius: 14px;
  display: grid;
  place-items: center;
  background: #fff3d6;
  font-size: 14px;
}

.nested {
  border: 2px solid #111;
  border-radius: 14px;
  padding: 10px;
  background: #fff;
}
  
.box #1
other
.box #2
.box #3
nested box
nested box

Snippet 1 uses .panel :nth-child(...) (descendants), so it may unexpectedly match nested content. Snippet 2 uses .panel > .row > :nth-child(...) (direct children), which is usually what you want for “nth-of-class” patterns.

Practical “Nth Of Class” Recipes

Highlight First And Last Of A Class

This is a super common UI pattern: first/last tag gets special styling.

.tags > :nth-child(1 of .tag) {
  background: #111;
  color: #fff;
}

.tags > :nth-last-child(1 of .tag) {
background: #111;
color: #fff;
} 
*,
::before,
::after {
  box-sizing: border-box;
}

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

.tags {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  padding: 14px;
  border: 3px solid #111;
  border-radius: 16px;
  background: #f6f6f6;
}

.tag,
.sep {
  padding: 8px 10px;
  border-radius: 999px;
  border: 2px solid #111;
  background: #eaf4ff;
  font-size: 14px;
}

.sep {
  background: #fff3d6;
  border-style: dashed;
}
  
separator Tag A Tag B separator Tag C Tag D separator

Zebra Striping On Only One Class

Imagine a list where some rows are “ads” or “notes” and you want striping only on real data rows. That’s exactly what odd of .row and even of .row are for.

.table > :nth-child(odd of .data-row) {
  background: #111;
  color: #fff;
}
  
.table > :nth-child(even of .data-row) {
  background: #111;
  color: #fff;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

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

.table {
  border: 3px solid #111;
  border-radius: 16px;
  overflow: hidden;
}

.data-row,
.note-row {
  display: grid;
  grid-template-columns: 1.2fr 1fr 0.8fr;
  gap: 10px;
  padding: 12px 14px;
  border-bottom: 2px solid #111;
  background: #fff;
}

.note-row {
  background: #fff3d6;
  border-bottom-style: dashed;
  font-size: 14px;
}

.cell {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.head {
  background: #f6f6f6;
  font-weight: 700;
}
  
Name
Role
Status
Ada
Engineer
Active
This row is not data. It should not affect zebra striping.
Bea
Designer
Active
Cam
Writer
Paused
Another non-data row.
Dee
PM
Active

Fallback Ideas When of S Isn’t Available

If you need a fallback for older browsers, CSS alone can’t fully replicate “nth-of-class” counting without the of clause. Here are the most common strategies:

  • Add wrapper elements so the siblings you care about become the only children in a container (then normal :nth-child() works).
  • Add helper classes server-side (or in your template system) like .is-first, .is-last, .is-odd, .is-even.
  • Use JavaScript to mark items at runtime when content is truly dynamic.

In modern projects, you’ll often be fine using the real thing and letting older browsers get the “unstyled” version. Use it as a progressive enhancement.

Cheat Sheet

  • First of class: :nth-child(1 of .item)
  • Last of class: :nth-last-child(1 of .item)
  • Second of class: :nth-child(2 of .item)
  • Second-to-last of class: :nth-last-child(2 of .item)
  • Odd of class: :nth-child(odd of .item)
  • Even of class: :nth-child(even of .item)
  • First 3 of class: :nth-child(-n + 3 of .item)
  • 2 through 4 of class: :nth-child(n + 2 of .item):nth-child(-n + 4 of .item)
  • Last 2 of class: :nth-last-child(-n + 2 of .item)
  • Count multiple selectors together: :nth-child(2 of .chip, .tag)

CSS Nth Of Class Conclusion

The of S clause is a game-changer for CSS selectors, finally giving us the ability to select “the nth of a class” without extra markup or JavaScript. It’s a bit more verbose than we’d like, but the power and flexibility it provides are well worth it.

And that’s your “nth-of-class” selector — not as a separate pseudo-class, but as a much more powerful upgrade to :nth-child().

Learn even more about :nth-child() and the of S clause in the CSS Nth Child (N of Selector) Interactive Tutorial.

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