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:
- Pick a parent.
- Look only at that parent’s element children (text nodes and pseudo-elements don’t count).
-
If you used
of S, filter that child list down to the children matchingS. -
Apply the
An+Bmath 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.cardin 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
);
}
SpacerCard A.card
SpacerCard 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 .itemInterruption .ad (should not affect zebra)Row 2 .itemRow 3 .itemAnother ad .adRow 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
);
}
SpacerCard 1.card
Card 2.card
SpacerCard 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 attributeBeta [data-featured]Gamma no attributeDelta [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 attributeGamma [data-featured]Delta no attributeEpsilon [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 cardCard Adata-state="open"
.cardCard Bdata-state="closed"
.cardCard Copen + featured
.cardAnother non-cardCard Dfeatured
.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-visibleBeta not visibleGamma [data-visible]Delta not visibleEpsilon .is-visible
Practical recipes with :nth-child(N of S)
Every 3rd featured card
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;
}
Card Afeatured
Ad / not a featured card
Card Bfeatured
Card Cnot featured
Card Dfeatured
Card Efeatured
Another non-featured thing
Card Ffeatured
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 hasdata-flag="true"and you actually meant[data-flag="true"]. -
You used a selector that can’t match the children you’re targeting (example:
of buttonbut your children areatags).
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. -
Ncan be a number,odd,even, or anAn+Bformula. -
Scan be a selector or a selector list (comma-separated).
Class selectors
-
First
.itemamong 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
.itemonly (zebra, ignoring other children):.item:nth-child(odd of .item) -
Even
.itemonly:.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
.cardonward:.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-visibleor[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.
