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.

SelectorExampleMatchesSpecificity
TypepEvery <p> element.(0,0,0,1)
Class.btnElements with class btn.(0,0,1,0)
ID#headerThe 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.

CombinatorExampleMatches
Descendantnav aAny a inside a nav, at any depth.
Childul > liDirect child li of ul.
Adjacent siblingh2 + pThe p immediately after an h2.
General siblingh2 ~ pEvery 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-classMatchesSpecificity
:hoverMouse is over the element.(0,0,1,0)
:focusElement has keyboard focus.(0,0,1,0)
:focus-visibleFocus from keyboard, not click.(0,0,1,0)
:activeElement is being activated.(0,0,1,0)
:checkedCheckbox or radio is checked.(0,0,1,0)
:disabledForm control is disabled.(0,0,1,0)
:requiredForm control has required.(0,0,1,0)
:valid / :invalidForm value passes / fails validation.(0,0,1,0)
:emptyElement has no children, including text.(0,0,1,0)
:targetElement matches the URL fragment.(0,0,1,0)
:rootThe document root (usually <html>).(0,0,1,0)
:link / :visitedAnchor 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-classMatches
:first-childFirst child of its parent.
:last-childLast child of its parent.
:only-childSole child of its parent.
:first-of-typeFirst element of its type among siblings.
:last-of-typeLast 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.

SelectorExampleWhat it doesSpecificity
:is(...):is(h1, h2, h3) + pList shorthand; takes the highest specificity inside.Highest in list
:where(...):where(h1, h2) + pLike :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-elementTargetsSpecificity
::beforeGenerated content before the element.(0,0,0,1)
::afterGenerated content after the element.(0,0,0,1)
::first-lineFirst rendered line of a block.(0,0,0,1)
::first-letterFirst letter of the first line.(0,0,0,1)
::markerList bullet or number.(0,0,0,1)
::placeholderForm control placeholder.(0,0,0,1)
::selectionCurrently selected text.(0,0,0,1)
::backdropBackdrop 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 !important make 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-child ignores element types. p:nth-child(2) matches p only if it is also the 2nd child overall.
  • :has is fully supported in evergreen browsers since 2023 but still missing in some embedded WebViews.
  • :is and :not inherit 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.