CSS Selectors Cheatsheet: Every Selector You'll Actually Use

Selectors are how CSS finds the elements you want to style — and the better you know them, the less BEM-style class soup you end up writing. When I audit a stylesheet at work, the first smell I look for is a wall of .card__header__title--large classes. Nine times out of ten, three well-chosen selectors would have replaced fifty class names. Here's the practical reference I actually use day to day — no obscure edge cases, just what ships in real production code.
Basic selectors
- * — universal (rarely used except in resets)
- tag — element type (h1, p, button)
- .class — the workhorse
- #id — id (avoid for styling; too specific)
- [attr=value] — attribute (great for form inputs)
Combinators — the four you must memorise
- A B — descendant (any depth)
- A > B — direct child only
- A + B — the very next sibling
- A ~ B — any later sibling
nav a { color: blue; } /* every anchor inside a nav */
nav > a { color: red; } /* only direct-child anchors */
h2 + p { margin-top: 0; } /* p right after an h2 */
h2 ~ p { color: gray; } /* every p after an h2 */Attribute selectors — my favourite hidden gem
a[href^="https"] { /* starts with */ }
a[href$=".pdf"] { /* ends with */ }
a[href*="youtube"] { /* contains */ }
input[type="email"] { /* exact match */ }I use a[href^="https"]:not([href*="mydomain.com"])::after to add an external-link icon on every article on this very site — zero JavaScript.
Powerful pseudo-classes
/* every other row */
tr:nth-child(even) { background: #f7f7f7; }
/* first 3 items */
li:nth-child(-n+3) { font-weight: 600; }
/* style a card when it contains an image */
.card:has(img) { padding-top: 0; }
/* style inputs only after the user has interacted */
input:user-invalid { border-color: red; }:has() — the parent selector we waited 20 years for
Available in every modern browser since late 2023, :has() lets you style an element based on its descendants. Before :has(), we had to add a wrapper class with JavaScript. Now the CSS just reacts.
/* form with an invalid field: highlight the whole card */
.card:has(input:invalid) { outline: 2px solid red; }
/* label whose input is checked */
label:has(input:checked) { background: #eef; }:is() and :where() — group selectors without repeating
/* Instead of h1 a, h2 a, h3 a { ... } */
:is(h1, h2, h3) a { color: inherit; }
/* :where() is identical but zero specificity — great for resets */
:where(ul, ol) { padding-left: 1.5rem; }Pseudo-elements — style parts that don't exist in HTML
- ::before / ::after — insert generated content
- ::first-line, ::first-letter — typographic touches
- ::marker — style list bullets/numbers directly
- ::placeholder — style the input placeholder text
- ::selection — the highlight color when text is selected
Specificity in one sentence
Inline > id > class/attr/pseudo-class > tag/pseudo-element. When in doubt, use a slightly more specific selector — never !important. If you're reaching for !important weekly, something upstream in your architecture is wrong.
Frequently asked questions
Is :has() safe to use in production?
Yes — it's supported in Chrome, Edge, Safari, and Firefox since 2023. For older browsers you can wrap it in a @supports selector(:has(a)) block and provide a fallback.
Should I use id selectors?
Rarely. They have very high specificity (100 vs 10 for a class) and make CSS hard to override. IDs are still useful as scroll anchors and JavaScript hooks — just avoid them in your stylesheet.
What's the difference between :is() and :where()?
Functionally identical, but :where() has zero specificity, so anything you write later can override it. :where() is perfect for resets and base styles; :is() is better when you want the specificity to stick.
Why avoid the universal * selector?
It matches every element — including inside third-party widgets you didn't write. Modern resets like Josh Comeau's use it deliberately with a narrow ruleset (box-sizing, margin: 0), but avoid * for anything opinionated.
External references
Enjoyed this article?
Share it with a fellow developer or explore more tutorials in our blog.
More articles