LEARNING OBJECTIVES ⌵
- Differentiate between index-based child selectors (
:first-child,:nth-child) and type-based selectors (:first-of-type,:nth-of-type). - Master the algebraic
An+Bformula to construct zebra stripes, ranges (-n+3), and repeating patterns. - Harness the modern
nth-child(An+B of S)selector to filter siblings matching a specific class. - Utilize modern logical pseudo-classes (
:is(),:where(),:not()) and control specificity vectors with surgical precision.
📖 The Mental Model & Story (Intuitive Foundation)
Imagine a high-school sports coach organizing students lined up in the gymnasium.
The coach gives two completely different kinds of instructions:
- Positional Instruction: "Every 3rd student in this line, take two steps forward!" The coach doesn't care if the 3rd person is a basketball player, a runner, or a swimmer—they are physically standing at index #3 in the line. This is
:nth-child(3n). - Category-Specific Instruction: "The first swimmer in this line, grab the stopwatch!" If the first three students in line are basketball players, the coach skips past them until encountering the very first person who is a swimmer. This is
:first-of-type.
In CSS, structural pseudo-classes give you this exact mathematical power over DOM node hierarchies.
Furthermore, modern CSS logic functions like :is(), :where(), and :not() act as logical operators (OR, ZERO-SPECIFICITY, and NOT), allowing you to condense dozens of repetitive selectors into a single readable line.
Technical Deep Dive & Specifications
1. Child vs. Type Selectors
The difference between :nth-child and :nth-of-type is a frequent source of bugs in frontend engineering:
DOM Parent Container <section>:
<h1>Title</h1> <-- 1st Child, 1st <h1> of type
<p>Paragraph 1</p> <-- 2nd Child, 1st <p> of type
<p>Paragraph 2</p> <-- 3rd Child, 2nd <p> of type
<div>Widget</div> <-- 4th Child, 1st <div> of type
<p>Paragraph 3</p> <-- 5th Child, 3rd <p> of type
p:first-child: Does NOT match anything! (Because the 1st child of the parent is<h1>, not<p>).p:first-of-type: Matches Paragraph 1 (The first sibling matching<p>).p:nth-child(2): Matches Paragraph 1 (It is the 2nd child overall, and it is a<p>).p:nth-of-type(2): Matches Paragraph 2 (The 2nd<p>among all<p>siblings).
2. The An+B Algebraic Formula
The argument inside :nth-child(An+B) or :nth-of-type(An+B) is evaluated as $A \times n + B$ for all non-negative integers $n \ge 0$:
+-----------------------------------------------------------------------------------------+
| THE An+B FORMULA CHEAT SHEET |
+-------------------+--------------------+------------------------------------------------+
| Expression | Math Evaluation | Target Matched Nodes |
+-------------------+--------------------+------------------------------------------------+
| `even` or `2n` | 0, 2, 4, 6, 8... | All even-indexed items (Zebra striping) |
| `odd` or `2n+1` | 1, 3, 5, 7, 9... | All odd-indexed items |
| `3n` | 0, 3, 6, 9, 12... | Every 3rd item (3, 6, 9, 12) |
| `3n + 1` | 1, 4, 7, 10... | Items 1, 4, 7, 10 (First of every 3-column row)|
| `-n + 3` | 3, 2, 1, 0, -1... | The FIRST 3 items (Indices 1, 2, 3) |
| `n + 5` | 5, 6, 7, 8... | ALL items starting from index 5 to infinity |
| `4` | 4 | Exactly the 4th item |
+-------------------+--------------------+------------------------------------------------+
RANGE SELECTOR: :nth-child(-n + 3)
Item #1 Item #2 Item #3 Item #4 Item #5
[ MATCH ] [ MATCH ] [ MATCH ] [ SKIP ] [ SKIP ]
\___________________________________/
First 3 Elements
3. CSS Selectors Level 4: nth-child(An+B of S)
Modern browsers support filtering :nth-child against a specific selector list:
/* Matches every 2nd .visible item, IGNORED hidden items in the count! */
li:nth-child(even of .visible) {
background-color: #334155;
}
4. Logic Pseudo-Classes: :is(), :where(), and :not()
+---------------------------------------------------------------------------------------------------+
| LOGICAL PSEUDO-CLASS SPECIFICITY RULES |
+-------------------+-----------------+-------------------------------------------------------------+
| Pseudo-Class | Specificity | Characteristics & Forgiving Parser |
+-------------------+-----------------+-------------------------------------------------------------+
| `:is(s1, s2)` | Highest of args | Replaces long duplicate selector lists. Forgiving parser. |
| `:where(s1, s2)` | ALWAYS (0,0,0,0)| Zero-specificity reset wrapper. Allows instant overrides. |
| `:not(s1, s2)` | Highest of args | Inverts condition (e.g. `button:not(.btn--primary)`). |
+-------------------+-----------------+-------------------------------------------------------------+
/* TRADITIONAL REPETITION: */
header h1, header h2, header h3,
main h1, main h2, main h3,
footer h1, footer h2, footer h3 {
color: #38bdf8;
}
/* MODERN :is() CONCISE EQUIVALENT: (Takes highest specificity among arguments) */
:is(header, main, footer) :is(h1, h2, h3) {
color: #38bdf8;
}
/* MODERN :where() ZERO-SPECIFICITY RESET: */
:where(header, main, footer) :where(h1, h2, h3) {
color: #38bdf8; /* Specificity is exactly (0, 0, 0, 0)! */
}
💻 Interactive Code Playground
Starter Code
Line-by-Line Code Breakdown
- Line 37 (
tbody tr:nth-child(even)): Applies a subtle background tint to alternate rows (rows 2, 4, 6...). - Line 45 (
tbody tr:nth-child(-n+3) td:first-child::before): Uses-n+3to match only rows 1, 2, and 3, prepending a fire emoji (🔥) to denote high-priority transactions. - Line 66 (
.btn:not(.btn--outline):not(:disabled)): Uses chained:not()logic pseudo-classes to apply the glowing blue primary background to any button that is neither an outline button nor disabled. - Line 83 (
:where(.prose) :where(p:not(:last-child))): Uses:where(). Despite its structural complexity, the total specificity remains exactly(0, 0, 0, 0), creating an ideal default system for design component resets.
Expected Browser Render Output
1. Transaction History (Zebra & Top 3 Matchers)
TRANSACTION ID SERVER NODE STATUS
🔥 TX-9011 us-east-1 Complete
🔥 TX-9012 eu-central-1 Complete (Dark Zebra tint)
🔥 TX-9013 ap-south-1 Pending
TX-9014 us-west-2 Complete (Dark Zebra tint)
TX-9015 sa-east-1 Failed
2. Negation Filtering with :not()
[ Primary Action (Solid Blue) ] [ Secondary (Outline) ] [ Disabled (Dim) ]🏋️ Hands-On Exercise
🎯 The Challenge: Build a 3-Column Responsive Pricing Table with Logic Selectors
Instructions:
- Create a 3-column pricing grid containing 3
.plan-cardelements. - Using
:nth-child(2)or:nth-of-type(2), highlight the middle "Pro" plan with a prominent cyan border and a top badge. - Using
:is(.plan-card, .faq-card) h3, style the headings across cards in one concise rule. - Using
:where(), set default padding and border radius on all cards with(0, 0, 0, 0)specificity so variant modifiers can override them cleanly. - In the feature lists, style the first item (
:first-child) in bold and dim the last item (:last-child) if it represents an excluded feature.
🏁 Starter Code Sandbox
⚠️ Common Pitfalls
- Assuming
:first-childfilters by tag type: Writingp:first-childfails if the<p>is preceded by an<h1>or<img>. Usep:first-of-typeif you want the first paragraph among sibling paragraphs. - Unintentional High Specificity with
:is()::is(.a, #b, .c)takes the specificity of its heaviest argument (#b= 0,1,0,0). Even if it matches.a, the rule will have ID-level specificity. Use:where()if you want zero specificity. - Confusing
:nth-child(3)with 0-indexed arrays: In CSS, DOM child indexing is 1-indexed (1is the first element,2is the second).
💡 Pro Tips
- CSS Resets with
:where(): Modern CSS component libraries wrap their foundational resets in:where(html, body, h1, ...)so consumers can override any style with single-class utility selectors without fighting library specificity. - The
nth-child(An+B of S)Power: Filter active search results or visible cards in real time without mutating classes:
.product:nth-child(even of :not([hidden])) {
background-color: #f8fafc;
}
📌 Key Takeaways
:nth-childis based on absolute sibling index position;:nth-of-typeis based on index among siblings of the identical tag name.- The
An+Bsyntax supports powerful formulas:even,odd,3n+1,-n+3(first 3), andn+4(4th and up). :is()groups selectors with a forgiving parser and takes the highest specificity among its arguments.:where()groups selectors with a forgiving parser and always has(0, 0, 0, 0)specificity.:not()negates matching conditions, enabling clean filtering without redundant class flags.- --