nth-child: Advanced Formulas
AI generated
{ }
@
CSS · Selectors · Frontend
nth-child: Advanced Formulas
Really understanding and applying the An+B math

Most developers only know nth-child through odd and even, yet behind it sits a complete linear formula that enables far more complex patterns. This collection explains the An+B math, nth-last-child and the of S syntax through concrete recipes for grids, lists and pagination.

17 min read nth-child · An+B formulas · nth-last-child All modern browsers

1. Why odd and even are only the surface of nth-child

Most tutorials reduce the nth-child pseudoclass to two keywords: odd for odd and even for even positions, usually demonstrated with zebra striped table rows. Those two keywords, though, are just named shortcuts for the actual, considerably more powerful syntax behind them, a linear formula of the form An+B that can express practically any repeating pattern across a list of siblings.

Anyone who only knows odd and even usually reaches for JavaScript or extra classes in the markup for more complex requirements, such as every third element starting from the fifth, or every element except the last two. With nth-child and the underlying formula, nearly any numeric position condition can be expressed directly in the selector. The following sections show how the formula works and which practical patterns can be derived from it.

2. The An+B formula explained step by step

The formula behind nth-child is An+B, where n stands for every non negative integer starting at zero, A determines the step size between matches, and B adds an offset. nth-child(3n+1) computes the positions 1, 4, 7, 10 and so on for n=0,1,2,3..., because three is added to the previous position each time, starting at one. Understanding this calculation is the key to using nth-child for arbitrary patterns instead of relying on premade keywords.

Negative values for A are also valid and effectively reverse the counting direction: nth-child(-n+3) computes the positions 3, 2, 1 and stops there because negative positions do not exist, which in practice selects exactly the first three elements. This property makes -n+X one of the most useful building blocks for range selection with nth-child, far more compact than a long enumeration of individual positions.


/* A = 3, B = 1: positions 1, 4, 7, 10, ... */
li:nth-child(3n+1) {
  font-weight: bold;
}

/* Negative A effectively selects "the first N" elements */
li:nth-child(-n+3) {
  color: #7c3aed;
}

/* A = 0 behaves like an exact position (equivalent to nth-child(5)) */
li:nth-child(0n+5) {
  background: #f5f3ff;
}

/* Classic odd/even are just named shortcuts for 2n+1 and 2n */
tr:nth-child(odd)  { background: #f8fafc; }  /* same as 2n+1 */
tr:nth-child(even) { background: #ffffff; }  /* same as 2n   */

3. Recipe: selecting ranges, excluding the first and last N

A common use case is selecting a range inside a list, for example every element between position four and eight. With nth-child, this can be solved by combining two formulas in one selector: li:nth-child(n+4):nth-child(-n+8) matches exactly positions four through eight, because the first formula captures everything from position four onward and the second formula limits it to position eight. The intersection of both conditions yields the desired range.

A second common pattern is excluding the first and last elements of a list, for example when separator lines should appear between entries but not before the first or after the last entry. li:not(:first-child):not(:last-child) solves this directly, while li:nth-child(n+2) alone already excludes the first element when no additional endpoint is needed. These range patterns with nth-child replace many cases that would otherwise require an extra CSS class on the first or last markup element.


/* Select exactly positions 4 through 8 */
li:nth-child(n+4):nth-child(-n+8) {
  background: #ede9fe;
}

/* Separator lines between items, but not before the first or after the last */
li:not(:first-child) {
  border-top: 1px solid #e2e8f0;
}

/* Fade out the last three items in a preview list */
li:nth-last-child(-n+3) {
  opacity: 0.5;
}

4. nth-last-child: using backward counting correctly

nth-last-child uses the same An+B math as nth-child, but counts from the end of the sibling list instead of the beginning. That makes it the ideal tool for patterns that depend on the total number of elements without explicitly knowing that number. li:nth-last-child(-n+3) always selects the last three elements, regardless of whether the list contains five or fifty entries in total, because counting always happens relative to the end.

A particularly elegant pattern combines nth-child and nth-last-child to find the exact middle element of a list, regardless of its length: li:nth-child(odd):nth-last-child(odd) only matches lists with an odd total length and automatically marks the middle element there, because only the exact middle has an odd position both from the front and from the back. This technique works entirely without JavaScript and without knowing the list length in advance.


/* Always the last three items, regardless of total list length */
li:nth-last-child(-n+3) {
  border-color: #7c3aed;
}

/* Find the exact middle element of an odd-length list */
li:nth-child(odd):nth-last-child(odd) {
  font-weight: 700;
  color: #6d28d9;
}

/* Last row of a grid needs no bottom border */
.grid-item:nth-last-child(-n+3) {
  border-bottom: none;
}

5. The of S syntax: understanding filtered counting

The extended syntax nth-child(An+B of S) adds an extra filter to the formula: instead of counting all siblings, only those additionally matching the selector S are counted. li:nth-child(2n of .featured) counts exclusively the elements carrying the class featured and selects every second one of those, regardless of which absolute positions those elements occupy within the full list.

This syntax solves a problem that classic nth-child alone could not solve: position counting within a filtered subset rather than the complete sibling list. Before the of S syntax, one either had to introduce an additional wrapper element per category or fall back to JavaScript to, for example, mark every second highlighted product in a mixed product list. Browser support for of S is now present across all current evergreen browsers.


/* Count only elements matching .featured, then select every 2nd of those */
li:nth-child(2n of .featured) {
  outline: 2px solid gold;
}

/* First 2 featured items in a mixed list, regardless of absolute position */
li:nth-child(-n+2 of .featured) {
  transform: scale(1.05);
}

6. Recipe: grid layouts with row break logic

In CSS grid layouts with a fixed column count, nth-child is the standard tool to specifically style elements at the start or end of a row, for example to insert horizontal separator lines only between rows, not between columns. In a grid with four columns, .item:nth-child(4n+1) always selects the first element of every row, which is useful for left aligned extra styling or removing a left margin at the start of the row.

A related pattern concerns variable border lines: .item:not(:nth-child(4n)) adds a right border to every element except the last one in every row, so no superfluous line appears at the row's end. This technique is far more robust than manually setting classes in the markup, because it stays automatically in sync with the actual position in the DOM, even when the number of elements changes due to dynamically loaded content.

7. nth-child vs. nth-of-type: the decisive difference

nth-child counts all sibling elements regardless of tag name, while nth-of-type only counts siblings of the same tag name. The difference becomes relevant as soon as different element types are mixed inside the same container: in a container with alternating h3 and p elements, p:nth-child(2) counts the absolute position among all children, while p:nth-of-type(2) counts the second p element regardless of how many h3 elements sit in between.

This difference is a common source of bugs: developers often expect the behavior of nth-of-type but accidentally use nth-child, producing wrong matches with mixed markup. The rule of thumb: use nth-of-type as soon as several different element types occur as siblings and only a specific type should be counted, use nth-child as soon as the absolute sibling position actually matters, for example in a homogeneous list consisting purely of li elements.

8. Recipe: pagination groups and separator lines

A practical recipe for nth-child is visually grouping pagination buttons or long lists into blocks, for example inserting a visual separator after every five entries. li:nth-child(5n)::after { content: ""; display: block; margin-block: 0.5rem; border-bottom: 1px solid #e2e8f0; } automatically inserts a separator line after every fifth element, without having to replicate the grouping in the markup.

For tables with many rows, the same principle can be used to draw a thicker separator line every five or ten rows, improving the readability of long datasets, a pattern familiar from printed spreadsheets. tr:nth-child(10n) marks every tenth row with a reinforced bottom border, which noticeably improves orientation especially in tables with hundreds of rows, entirely without additional grouping logic on the backend.

9. nth-child formulas compared directly

The following table shows common requirements and the matching nth-child formula for them, as a quick reference for daily project work.

Requirement Formula Explanation
First three elements :nth-child(-n+3) Negative A stops at position three
Last three elements :nth-last-child(-n+3) Counting from the end of the list
Range 4 through 8 :nth-child(n+4):nth-child(-n+8) Two formulas as an intersection
Every third filtered element :nth-child(3n of .tag) Counting only within the subset
All except first and last :not(:first-child):not(:last-child) Double negation instead of a formula

Combining several nth-child selectors as an intersection, as shown in the range example, is the key to more complex patterns. Instead of searching for a single, complicated formula, almost every requirement can be assembled from simple, easily understood sub formulas.

Mironsoft

Grid layouts, lists and Hyvä themes without unnecessary markup

Position based styling without extra classes in the markup?

We replace manually maintained position classes with precise nth-child formulas, for leaner markup and grid layouts that automatically grow with dynamic content.

CSS Audit

Identifying manual position classes and replacing them with nth-child

Grid Refactoring

Making row break logic and separator lines robust against dynamic content

Hyvä Integration

Cleanly integrating nth-child patterns into Tailwind and Alpine components

10. Summary

nth-child is far more than odd and even: the underlying An+B formula allows arbitrary step sizes, offsets and, through negative values, range selection as well. nth-last-child counts the same formula backward from the end of the list, enabling patterns that work independently of the total element count. The of S syntax adds filtered counting, allowing position selection within a subset rather than the complete sibling list.

Anyone mastering these formulas can express grid row breaks, pagination groups, range selection and position filtering directly in the selector, without maintaining extra classes in the markup. The difference between nth-child and nth-of-type remains the most common source of errors and should always be checked deliberately with mixed markup before a formula is considered finished.

nth-child Formulas: The Key Points at a Glance

An+B Basic Formula

A determines the step size, B the offset. Negative A produces range selection starting at position one.

nth-last-child

Counts from the end of the list, ideal for patterns that should work independently of the total count.

of S Syntax

Counts only elements additionally matching a filter, for position selection within a subset.

nth-child vs. nth-of-type

nth-child counts all siblings, nth-of-type only those with the same tag name. Common confusion with mixed markup.

11. FAQ: nth-child Formulas

1What does An+B mean?
A determines step size, B an offset, for n=0,1,2,... a series of positions results.
2Select the first three elements?
With nth-child(-n+3), negative A stops at position three.
3Difference from nth-last-child?
Same formula, but counting from the end of the list instead of the beginning.
4Select the range 4 through 8?
nth-child(n+4):nth-child(-n+8) forms the intersection of both conditions.
5What does of S do?
Counts only elements additionally matching a filter, instead of all siblings.
6Difference nth-child to nth-of-type?
nth-child counts all siblings, nth-of-type only those with the same tag name.
7Middle element without JavaScript?
li:nth-child(odd):nth-last-child(odd) matches the exact middle of an odd length list.
8First element of every grid row?
With four columns, nth-child(4n+1) always matches the first element of every row.
9Separator lines every N elements?
With li:nth-child(5n) a separator can be inserted after every fifth element.
10Is of S supported everywhere?
Yes, all current evergreen browsers fully support the of S syntax.