CSS :nth-child() and Structural Pseudo-Classes Explained
The :nth-child() family selects elements by position among siblings, using a simple an+b formula. How it works and where it beats manually added classes.
:nth-child() is a CSS pseudo-class that selects elements based on their position among sibling elements, using a formula of the form an+b. It lets you style “every third row,” “the first four items,” or “everything but the last one” without touching the markup or adding a single class. It’s part of a broader family of structural pseudo-classes that select by position rather than by attribute or content.
The an+b formula
The syntax looks unfamiliar at first but follows one consistent rule: n starts at 0 and counts up, and the browser evaluates the formula for each value of n to produce a list of matching positions.
li:nth-child(3) /* only the 3rd child */
li:nth-child(2n) /* every 2nd child: 2, 4, 6... */
li:nth-child(2n+1) /* every odd child: 1, 3, 5... */
li:nth-child(n+4) /* the 4th child onward */
li:nth-child(-n+3) /* only the first 3 children */
Two keywords cover the most common cases without the formula: :nth-child(odd) and :nth-child(even) are shorthand for 2n+1 and 2n. Striping a table is just tr:nth-child(even) { background: #f4f4f4; }.
The full family
:nth-child(an+b)— matches by position among all siblings, regardless of element type.:nth-of-type(an+b)— matches by position among siblings of the same element type, ignoring other tags in between.:nth-last-child()/:nth-last-of-type()— count from the end instead of the start.:first-child/:last-child— shorthand for:nth-child(1)and:nth-last-child(1).:only-child— matches an element that is the sole child of its parent.
The :nth-child vs :nth-of-type distinction trips people up most. Given <p>, <img>, <p>, <p>, img:nth-of-type(1) matches that one image regardless of its position among all children, while img:nth-child(2) would only match if the image happened to be the second child overall.
The of S syntax
Modern :nth-child() supports an optional of <selector> clause that filters which siblings count before applying the position formula: :nth-child(2n of .featured) selects every second element among only those matching .featured, ignoring non-matching siblings entirely for counting purposes. This is useful for things like “every other visible item” when some items are conditionally hidden or excluded by a class.
Why this beats manual classes
Before these selectors were reliable across browsers, striping a list or highlighting specific positions meant adding a class like .even or .first in the markup, or doing it with JavaScript. That couples presentation to markup and breaks the moment an item is inserted or removed — the classes have to be recalculated. :nth-child() computes the position live, in CSS, so reordering or filtering the underlying list just works. It pairs well with CSS custom properties for building configurable list styles, and with :has() and :is()/:where() when you need to combine positional and relational logic in one selector.
Practical patterns
Zebra-striped tables:
tbody tr:nth-child(even) { background: var(--row-alt); }
A responsive grid where only the first 3 items get emphasis:
.card:nth-child(-n+3) { grid-column: span 2; }
Removing a border from every last item in a row of 4:
.item:nth-child(4n) { border-right: none; }
Alternating an animation delay per item:
.item:nth-child(odd) { animation-delay: 0.1s; }
.item:nth-child(even) { animation-delay: 0.2s; }
Performance and specificity notes
Structural pseudo-classes carry the same specificity as a regular class selector, so li:nth-child(2n) behaves like li.some-class for cascade purposes — see how CSS specificity works if that ordering ever surprises you. They’re also cheap: the browser already tracks sibling relationships for layout, so evaluating a position formula adds negligible overhead even in long lists.
The takeaway
:nth-child() and its relatives select elements by their position among siblings using an an+b formula, with odd/even and first-child/last-child covering the common cases as shorthand. Use :nth-of-type() when you care about position among a specific tag rather than all siblings, and reach for the of <selector> syntax when you need to filter which siblings count before positions are computed. All of it runs in CSS, so the list can reorder, grow, or shrink without any style having to be recalculated by hand.
Tagged
Keep reading
Takina · · 4 min read CSS object-fit and object-position, Explained
object-fit controls how an image or video is cropped inside its box, and object-position controls which part of it stays visible. How they work together.
Takina · · 5 min read CSS inherit, initial, unset & revert Explained
CSS's four global keywords control where a property's value comes from. How inherit, initial, unset, and revert differ, with a comparison table.
Takina · · 4 min read Dynamic Viewport Units: dvh, svh, and lvh Explained
dvh, svh, and lvh fix the classic mobile vh bug where browser toolbars cut off full-height layouts. Here's what each unit measures and when to use it.