CSS selectors target elements for styling using type, class, ID, attribute, and pseudo-class/element patterns, with specificity calculated as a weighted score (inline > ID > class > element) that determines which rule wins when multiple selectors match the same element.
Specificity is calculated as (IDs, Classes/Attributes/Pseudo-classes, Types/Pseudo-elements). A single ID selector (1,0,0) always outweighs any number of class selectors (0,n,0).
When multiple rules conflict, the browser checks origin/importance first, then cascade layers, then specificity, and finally source order — specificity is only one factor among several.
:is() groups selectors with the highest argument's specificity, :where() groups with zero specificity, and :has() enables parent selection based on children — all solving longstanding CSS limitations.
Descendant (space) matches any depth, child (>) matches direct children only, adjacent sibling (+) matches the next sibling, and general sibling (~) matches all following siblings.
CSS selectors and specificity are among the most commonly tested CSS topics in front-end interviews. Understanding how the browser decides which styles to apply when multiple rules conflict is fundamental to writing maintainable CSS.
Simple selectors:
div, p, a — matches element names.card, .active — matches class attribute values#header — matches id attribute (should be unique per page)* — matches all elements[type="text"], [href^="https"] — matches attribute values with optional pattern matchingCombinators:
article p — any p inside article (any depth)article > p — direct child p of article onlyh2 + p — the first p immediately after h2h2 ~ p — all p elements after h2 at the same levelPseudo-classes (select elements by state):
:hover, :focus, :active — user interaction states:first-child, :last-child, :nth-child(n) — structural position:not(selector) — negation:is(selector-list) — matches any selector in the list with the highest specificity of its arguments:where(selector-list) — same as :is() but with zero specificity (great for defaults that are easy to override):has(selector) — the "parent selector" that matches elements containing specific children. :has() is revolutionary because CSS historically could never select parents based on children.Pseudo-elements (create virtual elements):
::before, ::after — generate content before/after the element::first-line, ::first-letter — style text portions::placeholder — style input placeholder text::selection — style highlighted textSpecificity is calculated as a three-component weight (a, b, c):
Examples:
p → (0, 0, 1).card → (0, 1, 0)#header → (1, 0, 0)#header .nav li.active → (1, 2, 1)div.card:hover → (0, 2, 1)Higher specificity wins. If specificity is equal, the last rule in source order wins.
Inline styles (style="...") beat all selector-based specificity. !important beats inline styles but should be a last resort.
The most common specificity issue: styles from a component library have higher specificity than your custom styles. Solutions:
!important (last resort):where() in the library for zero-specificity defaults@layer) to control priority regardless of specificityThese modern pseudo-classes solve longstanding problems:
:is() reduces repetition: :is(h1, h2, h3) a replaces h1 a, h2 a, h3 a. Its specificity is the highest specificity of its arguments.
:where() works identically but has zero specificity — perfect for base styles that should be easy to override.
:has() is the long-awaited parent selector. article:has(img) selects articles that contain images. form:has(:invalid) selects forms with invalid inputs. This opens up styling patterns that previously required JavaScript.
When multiple rules match an element, the browser resolves conflicts in this order:
!important)@layer)Understanding this full algorithm is what separates junior from senior CSS knowledge.
Fun Fact
The :has() pseudo-class was called 'the holy grail of CSS selectors' for years because it enables parent selection — something developers had requested since CSS2 in 1998. It finally shipped in all major browsers in late 2023, a 25-year wait.