Type a valid or invalid email. The form border changes color using :has(:invalid).
1. :has() - The Parent Selector
:has() selects an element based on what it contains. It's the long-awaited CSS parent selector. Style a form based on its input state, a card when its checkbox is checked, and more.
Cards highlight when their checkbox is checked. Pure CSS with :has(:checked).
/* Style parent based on child state */
.form:has(input:invalid) {
border-color: red;
background: #fef2f2;
}
.form:has(input:valid) {
border-color: green;
background: #f0fdf4;
}
/* Card highlights when checkbox inside is checked */
.card:has(input:checked) {
border-color: #6366f1;
background: #eef2ff;
}
/* Show sibling when input has content */
.field:has(input:not(:placeholder-shown)) .hint {
display: block;
}
/* Style based on child element presence */
.card:has(img) { /* card with an image */ }
.card:has(> h2) { /* card with a direct h2 child */ }2. :is() and :where()
Both let you group selectors to avoid repetition. The key difference: :is() takes the highest specificity of its arguments, while :where() always has zero specificity.
Heading 3
Heading 4
Heading 5
All headings styled indigo via :is(h3, h4, h5)
Specificity = highest selector in the list (e.g., same as h3).
Heading 3
Heading 4
Heading 5
Same styling via :where(h3, h4, h5), easy to override
Specificity = 0. Perfect for default/reset styles that are easy to override.
/* Without :is() - repetitive */
article h1, article h2, article h3,
section h1, section h2, section h3 {
color: navy;
}
/* With :is() - clean and concise */
:is(article, section) :is(h1, h2, h3) {
color: navy;
}
/* :where() for low-specificity defaults */
:where(h1, h2, h3) { color: inherit; }
/* Key difference: specificity */
:is(#id, .class) p { } /* specificity: (1,0,1), takes #id */
:where(#id, .class) p { } /* specificity: (0,0,1), always zero */
/* Great for CSS resets */
:where(ul, ol) { list-style: none; padding: 0; }3. :not() - Negation
:not() excludes elements matching the given selector. Modern CSS supports complex selectors and comma-separated lists inside :not().
Only non-disabled items get hover effects via li:not(.disabled):hover.
li:not(:last-child) adds borders to all but the last item.
/* Exclude specific class */
li:not(.disabled):hover { background: #eef2ff; }
/* All but last child */
li:not(:last-child) { border-bottom: 1px solid #e5e7eb; }
/* Multiple exclusions (modern CSS) */
input:not([type="submit"], [type="reset"]) {
border: 2px solid #e5e7eb;
}
/* Complex selectors */
.card:not(:has(img)) { min-height: 10rem; }
/* Exclude first and last */
li:not(:first-child):not(:last-child) {
padding: 1rem;
}4. Attribute Selectors
Target elements by their attributes using pattern-matching operators: starts-with (^=), ends-with ($=), contains (*=), and more.
Links styled by attribute pattern: [href^="https"], [href$=".pdf"], etc.
/* Attribute presence */
[data-tooltip] { cursor: help; }
/* Exact match */
[type="email"] { border-color: blue; }
/* Starts with */
a[href^="https"] { color: green; } /* external links */
a[href^="mailto"] { color: blue; } /* email links */
a[href^="#"] { color: purple; } /* anchor links */
/* Ends with */
a[href$=".pdf"] { color: red; } /* PDF links */
a[href$=".zip"] { color: orange; } /* ZIP downloads */
/* Contains */
a[href*="example"] { font-weight: bold; }
/* Custom data attributes */
[data-theme="dark"] { background: #1e293b; }
[data-size="lg"] { font-size: 1.25rem; }
/* Case-insensitive flag */
a[href$=".PDF" i] { color: red; } /* matches .pdf, .PDF, .Pdf */5. Combinators
Combinators define the relationship between selectors: descendant (space), child (>), adjacent sibling (+), and general sibling (~).
> only targets direct children, not deeper descendants.
(adjacent)
(general)
(general)
+ = next sibling only. ~ = all following siblings.
/* Descendant (space): any depth */
article p { line-height: 1.6; }
/* Child (>): direct children only */
.nav > li { display: inline-block; }
/* Adjacent sibling (+): next sibling only */
h2 + p { margin-top: 0; } /* first paragraph after heading */
input + .error { color: red; } /* error right after input */
/* General sibling (~): all following siblings */
h2 ~ p { color: #4b5563; } /* all paragraphs after heading */
input:invalid ~ .hint { display: block; }
/* Practical examples */
.checkbox:checked + label { font-weight: bold; }
img + figcaption { font-style: italic; }
details[open] > summary { color: blue; }6. :nth-child() Patterns
Select elements by position using keywords (odd, even) or the An+B formula. The of S syntax adds type-filtering.
Highlights 1st, 3rd, 5th, 7th, 9th items.
Every 3rd item: 3, 6, 9.
First 3 items only. -n+3 counts down: 3, 2, 1.
Last 2 items. :nth-last-child counts from the end.
/* Keywords */
tr:nth-child(odd) { background: #f9fafb; } /* zebra stripes */
tr:nth-child(even) { background: white; }
/* An+B formula */
li:nth-child(3n) { } /* every 3rd: 3, 6, 9... */
li:nth-child(3n+1) { } /* 1st of every group of 3: 1, 4, 7... */
li:nth-child(-n+3) { } /* first 3 only: 3, 2, 1 */
li:nth-child(n+4) { } /* from 4th onward: 4, 5, 6... */
/* From the end */
li:nth-last-child(-n+2) { } /* last 2 items */
li:nth-last-child(1) { } /* last item (same as :last-child) */
/* "of S" syntax (newest) */
li:nth-child(2 of .highlight) { } /* 2nd highlighted item */
p:nth-child(odd of :not(.hidden)) { } /* odd among visible paragraphs */7. :focus-visible vs :focus
:focus-visible only shows focus styles when the user navigates via keyboard, not on mouse click. Better UX than :focus for buttons and links.
Click each button (no outline on :focus-visible), then Tab to them (outline appears). :focus always shows outline.
/* :focus - always shows (mouse + keyboard) */
button:focus {
outline: 3px solid #6366f1;
outline-offset: 2px;
}
/* :focus-visible - only keyboard navigation */
button:focus-visible {
outline: 3px solid #6366f1;
outline-offset: 2px;
}
/* Best practice: remove default, add focus-visible */
button:focus {
outline: none;
}
button:focus-visible {
outline: 3px solid #6366f1;
outline-offset: 2px;
}
/* Combined: subtle focus + strong focus-visible */
input:focus {
border-color: #6366f1;
}
input:focus-visible {
outline: 3px solid #6366f1;
outline-offset: 2px;
}