Overview
Specificity decides which rule wins. This card lists every selector type, what it matches, and the specificity score it contributes. The score is a tuple (inline, IDs, classes/attrs/pseudo-classes, types/pseudo-elements); higher tuples win. For deeper coverage on cascade layers and modern selectors, see css-cascade-layers and css-has-selector.
Basic selectors
The selectors a CSS file leans on for most rules.
| Selector | Example | Matches | Specificity |
|---|---|---|---|
| Type | p | Every <p> element. | (0,0,0,1) |
| Class | .btn | Elements with class btn. | (0,0,1,0) |
| ID | #header | The element with id header. | (0,1,0,0) |
| Universal | * | Every element. | (0,0,0,0) |
| Attribute (present) | [disabled] | Elements that have the attribute. | (0,0,1,0) |
| Attribute (equals) | [type="email"] | Exact match. | (0,0,1,0) |
| Attribute (prefix) | [href^="https"] | Starts with. | (0,0,1,0) |
| Attribute (suffix) | [src$=".svg"] | Ends with. | (0,0,1,0) |
| Attribute (contains) | [class*="card"] | Substring match. | (0,0,1,0) |
Reach for IDs only when no class will do. The (0,1,0,0) score is hard to override without !important or layer stacking.
Combinators
How selectors compose left to right.
| Combinator | Example | Matches |
|---|---|---|
| Descendant | nav a | Any a inside a nav, at any depth. |
| Child | ul > li | Direct child li of ul. |
| Adjacent sibling | h2 + p | The p immediately after an h2. |
| General sibling | h2 ~ p | Every p after an h2 in the same parent. |
The child combinator is faster than descendant for the browser; both are fast enough for any real-world page.
Pseudo-classes (state)
State-based selectors react to user input, structure, or form data.
| Pseudo-class | Matches | Specificity |
|---|---|---|
:hover | Mouse is over the element. | (0,0,1,0) |
:focus | Element has keyboard focus. | (0,0,1,0) |
:focus-visible | Focus from keyboard, not click. | (0,0,1,0) |
:active | Element is being activated. | (0,0,1,0) |
:checked | Checkbox or radio is checked. | (0,0,1,0) |
:disabled | Form control is disabled. | (0,0,1,0) |
:required | Form control has required. | (0,0,1,0) |
:valid / :invalid | Form value passes / fails validation. | (0,0,1,0) |
:empty | Element has no children, including text. | (0,0,1,0) |
:target | Element matches the URL fragment. | (0,0,1,0) |
:root | The document root (usually <html>). | (0,0,1,0) |
:link / :visited | Anchor link state. | (0,0,1,0) |
Use :focus-visible for focus rings; it skips the ring on mouse clicks while keeping it for keyboard users.
Pseudo-classes (structural)
Position within a parent. The n is a 1-based index.
| Pseudo-class | Matches |
|---|---|
:first-child | First child of its parent. |
:last-child | Last child of its parent. |
:only-child | Sole child of its parent. |
:first-of-type | First element of its type among siblings. |
:last-of-type | Last element of its type among siblings. |
:nth-child(2) | Second child of its parent. |
:nth-child(odd) | 1st, 3rd, 5th, … |
:nth-child(2n+1) | Same as odd. |
:nth-child(3n) | Every third child. |
:nth-last-child(1) | Counted from the end; same as :last-child. |
:nth-of-type(2) | Second of its type. |
:nth-child counts siblings of any type; :nth-of-type counts siblings of the same type. They are not interchangeable.
Modern logical pseudo-classes
The selectors that changed how complex rules are written.
| Selector | Example | What it does | Specificity |
|---|---|---|---|
:is(...) | :is(h1, h2, h3) + p | List shorthand; takes the highest specificity inside. | Highest in list |
:where(...) | :where(h1, h2) + p | Like :is but contributes (0,0,0,0). | (0,0,0,0) |
:not(...) | li:not(.skip) | Negation; takes the highest specificity inside. | Highest in list |
:has(...) | article:has(img) | Parent selector; matches article that contains an img. | Highest in list |
:where is the right tool for resets and base styles; its zero specificity will not fight component CSS.
Pseudo-elements
Pseudo-elements address parts of an element that are not separate elements.
| Pseudo-element | Targets | Specificity |
|---|---|---|
::before | Generated content before the element. | (0,0,0,1) |
::after | Generated content after the element. | (0,0,0,1) |
::first-line | First rendered line of a block. | (0,0,0,1) |
::first-letter | First letter of the first line. | (0,0,0,1) |
::marker | List bullet or number. | (0,0,0,1) |
::placeholder | Form control placeholder. | (0,0,0,1) |
::selection | Currently selected text. | (0,0,0,1) |
::backdrop | Backdrop of a <dialog> or fullscreen element. | (0,0,0,1) |
Pseudo-elements use double colons (::); the single-colon form (:before) is the CSS2 spelling and still works for the originals.
Common gotchas
- ID selectors and
!importantmake rules hard to override. Prefer classes and cascade layers. :not(.a.b)does not mean “not (a and b)” in older browsers; pass each as separate arguments::not(.a):not(.b).:nth-childignores element types.p:nth-child(2)matchesponly if it is also the 2nd child overall.:hasis fully supported in evergreen browsers since 2023 but still missing in some embedded WebViews.:isand:notinherit the highest specificity of their arguments;:is(.a, #b)scores (0,1,0,0).- Attribute selectors are case-sensitive by default. Use
[type="email" i]for case-insensitive match.