:has(), :not() & :nth-child()
Smarter selectors
Lesson 5 of 20 · 11 min · Module 2: Selectors and the cascade
Some styles depend on context: highlight the plan whose radio button is checked, stripe every other row, draw a line between items but not after the last one. Modern selectors express these rules directly, without extra classes or JavaScript.
Start this lesson →Free. No installation: you write and run the code in your browser, and it is checked automatically.
By the end, you’ll be able to…
- Style an element based on what it contains with :has()
- Select by position with :nth-child()
- Exclude elements with :not()
The idea
.plan:has(input:checked) selects a plan that contains a checked input, the long-awaited "parent selector". :nth-child(even) selects every second item, and :not(:last-child) selects every item except the last. :is() and :where() group selectors; :where() adds no specificity at all.
/* a card that contains an image */
.card:has(img) { padding-top: 0; }
/* every h2 and h3 inside an article */
article :is(h2, h3) { color: #2f6f4e; }
01A parent that reacts to its children
.plan:has(input:checked) matches the label whose radio button is checked. Click another plan in the preview and the highlight follows, with no JavaScript involved.
02Counting children
nth-child(even) matches the 2nd, 4th, 6th… child. nth-child(3n+1) matches the 1st, 4th, 7th…, and nth-child(-n+3) the first three.
li:nth-child(odd) /* 1, 3, 5 … */
li:nth-child(3n) /* 3, 6, 9 … */
li:nth-child(-n+3) /* the first three */
03Specificity of :is() and :where()
article :is(h2, .title) takes the specificity of its most specific argument. :where() always counts as zero, which makes it ideal for defaults that should be easy to override.
Your turn
border-color: #2f6f4e and background: #eef6e5, using .plan:has(input:checked). 2) Give every even .features li the background #f6f8f2. 3) Give every feature row except the last one border-bottom: 1px solid #e3ead7.What the checks look for
- The checked plan has a green border
- The checked plan has a light green background
- The other plans keep the neutral border
- Even rows are striped
- Odd rows stay plain
- Rows are separated by a line
- The last row has no line
Take this with you
:has(), :nth-child() and :not() replace many helper classes.
Go a little further
Use .features:has(li:nth-child(5)) to switch the list to two columns as soon as it has five or more items.