CSS selectors and specificity reference
The selectors you will use every day, how the browser decides which rule wins when several match, and practical ways to keep specificity low and predictable.
A selector picks which elements a CSS rule applies to. When several rules set the same property on the same element, the cascade decides which one wins, and specificity is the part of that decision developers bump into most often.
Everyday selectors
p: type selector, every<p>..card: class selector.#main: ID selector.[type="email"]: attribute selector. Also[href^="https"](starts with),[href$=".pdf"](ends with),[class*="btn"](contains).*: universal selector.a:hover,input:focus-visible,li:first-child,li:nth-child(2n),input:checked,button:disabled: pseudo-classes, matching a state or position.p::first-line,.note::before,input::placeholder: pseudo-elements, styling a part of an element.
Combinators
nav a: descendant, anyainsidenav.ul > li: direct child only.h2 + p: the paragraph immediately after anh2.h2 ~ p: every later sibling paragraph.
Logical pseudo-classes
:is(h1, h2, h3) a: matches any of the list, shortening repetitive selectors.:where(...): same as:is()but with zero specificity, ideal for defaults that are easy to override.:not(.active): excludes matches..card:has(img): matches a card that contains an image. Supported in all current major browsers.
How specificity is calculated
Specificity is compared as three numbers, written (A, B, C):
- A: number of ID selectors.
- B: classes, attribute selectors, and pseudo-classes.
- C: type selectors and pseudo-elements.
Compare A first; only if it ties, compare B, then C. The universal selector and combinators add nothing. :is(), :not(), and :has() take the specificity of their most specific argument, and :where() always counts as zero.
p /* (0, 0, 1) */
.card p /* (0, 1, 1) */
nav a:hover /* (0, 1, 2) */
#main .card /* (1, 1, 0) */
:where(#main) .card /* (0, 1, 0) */
.btn.btn-primary /* (0, 2, 0) */
Because the comparison goes column by column, one ID beats any number of classes: #main wins over .a.b.c.d.e.f.
The rest of the cascade
Specificity is not the first check. In simplified order, the browser considers:
- Origin and importance:
!importantdeclarations beat normal ones. - Cascade layers: for normal declarations, styles in a later
@layerbeat earlier layers, and unlayered styles beat all layers, regardless of specificity. - Specificity.
- Source order: if everything else ties, the rule that appears last wins.
Inline style="..." attributes beat any selector unless the other rule uses !important.
Keeping it manageable
- Style with classes, not IDs. Keep most selectors at one or two classes.
- Avoid long descendant chains like
.page .sidebar ul li a; they are fragile and hard to override. - Put resets and library defaults inside
:where()or a low@layerso your own styles win without a fight. - Treat
!importantas a last resort, mostly for utility classes that must always apply. - In DevTools, the Styles panel lists matching rules in winning order and crosses out overridden declarations. Hovering a selector shows its specificity in Chromium-based browsers.
See also
- 0082 HTTP status codes: the common ones and what to do A quick reference to the HTTP status codes you will meet most often, what each one means, and the first thing to… Beginner · Reference · Any system Article References · Status codes 15 Oct 2026
Repos in the wild
- MDN Web Docs content (mdn/content) mdn/content The source of MDN Web Docs: the Markdown pages behind the most widely used reference for HTML, CSS, JavaScript, HTTP, and Web APIs. GitHub
History
Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.
- commitpedia-admin +3,127 chars