# CSS Selectors Cheatsheet
Selectors decide which elements a rule targets; specificity decides which rule wins when two selectors both match the same element. Most "my CSS isn't applying" bugs are a specificity or source-order problem, not a syntax problem — this sheet covers both.
Quick reference
Basic selectors
| Selector |
Matches |
Example |
| `*` |
Every element |
`* { box-sizing: border-box; }` |
| `tag` |
All elements of that tag |
`p { }` |
| `.class` |
Elements with that class |
`.card { }` |
| `#id` |
The element with that id |
`#header { }` |
| `[attr]` |
Elements with the attribute present |
`[disabled] { }` |
| `[attr="val"]` |
Exact attribute value |
`[type="submit"] { }` |
| `[attr~="val"]` |
Attribute is a space-separated list containing `val` |
`[class~="active"]` |
| `[attr^="val"]` |
Attribute value starts with `val` |
`[href^="https"]` |
| `[attr$="val"]` |
Attribute value ends with `val` |
`[href$=".pdf"]` |
| `[attr*="val"]` |
Attribute value contains `val` |
`[class*="col-"]` |
Combinators
| Combinator |
Meaning |
Example |
| `A B` (space) |
B is any descendant of A |
`nav a` |
| `A > B` |
B is a direct child of A |
`ul > li` |
| `A + B` |
B immediately follows A (same parent) |
`h2 + p` |
| `A ~ B` |
B is any later sibling of A (same parent) |
`h2 ~ p` |
Common pseudo-classes
| Selector |
Matches |
| `:hover` / `:focus` / `:active` |
Interaction states |
| `:first-child` / `:last-child` |
Position among siblings, all sibling types count |
| `:nth-child(n)` |
nth position; also accepts `2n`, `2n+1`, `odd`, `even` |
| `:not(sel)` |
Excludes elements matching `sel` |
| `:is(a, b, c)` |
Matches if any of the list matches — takes the *highest* specificity in the list |
| `:where(a, b, c)` |
Same matching as `:is()` but **always contributes zero specificity** |
| `:has(sel)` |
Matches an element if it contains something matching `sel` (a real "parent selector") |
| `:checked` / `:disabled` |
Form element state |
Pseudo-elements
| Selector |
Targets |
| `::before` / `::after` |
Generated content (requires a `content` property) |
| `::first-line` / `::first-letter` |
First line/letter of block text |
| `::placeholder` |
An input's placeholder text |
| `::selection` |
User-highlighted text |
Common patterns
Specificity, calculated
Specificity is a 3-part tuple, compared left to right — it is not simple addition:
| Selector type |
Weight |
| Inline `style="..."` |
Beats every selector below |
| ID selectors (`#id`) |
(1, 0, 0) per ID |
| Class, attribute, pseudo-class (`.class`, `[attr]`, `:hover`) |
(0, 1, 0) each |
| Type selectors, pseudo-elements (`div`, `::before`) |
(0, 0, 1) each |
| Universal `*`, combinators |
0 — no specificity contribution |
#nav .item /* (1,1,0) */
.nav .item.active /* (0,3,0) */
nav ul li a /* (0,0,4) */
(1,1,0) beats both of the others — one ID outweighs any number of classes, and any number of classes outweighs any number of type selectors, regardless of total count.
Target every child except the last
li:not(:last-child) {
border-bottom: 1px solid #ddd;
}
Style a label only when its input is checked
input:checked + label {
font-weight: bold;
}
Style a card only if it contains an image (real parent selector)
.card:has(img) {
padding-top: 0;
}
Pitfalls
!important beats specificity entirely, then specificity breaks ties among !important rules: reaching for !important to win a cascade fight usually means the real fix is lowering some other rule's specificity — it doesn't scale, since the next override needs !important too.
- Two equally specific rules: the one later in source order wins, not the more "specific-looking" one. A
.button { color: red; } defined after .button { color: blue; } in the same stylesheet always wins, regardless of which rule "seems" more important.
:is() takes on the specificity of its most specific argument; :where() always contributes zero: :is(#id, .class) has ID-level specificity because of #id inside it — a frequent surprise when people assume :is()/:where() are specificity-neutral wrappers.
nth-child counts ALL sibling types, not just same-tag siblings: p:nth-child(2) only matches if the actual 2nd child of the parent happens to be a <p> — it does not mean "the 2nd <p>." Use :nth-of-type() when you want to count only same-tag siblings.
- Attribute selectors are case-sensitive by default for the value (except in HTML's boolean/enumerated attributes) —
[href$=".PDF"] will not match .pdf. Add i before the closing bracket for a case-insensitive match: [href$=".pdf" i].
Related ZipKit tools
- Color Picker — pick and convert the exact color values you're targeting with attribute or class selectors.
- Box Shadow Builder — generate
box-shadow values to pair with :hover/:focus state selectors.
- SVG Path Editor & Visualizer — style inline SVG elements that
::before/::after can't reach directly.
Related cheatsheets