
Quick answer: CSS specificity decides which rule wins when two selectors target the same element: inline styles beat ids, ids beat classes, and classes beat elements. A CSS rule has a selector and a declaration block, and selectors range from simple (element, class, id) to relational (descendant, child, attribute, pseudo-class). inline styles beat ids, ids beat classes, and classes beat elements. When specificity fights you, fix the selector rather than adding more rules.
Table of Contents
The selector types you actually use
| Selector | Matches | Example |
|---|---|---|
p | All paragraphs | p { margin: 0 } |
.card | Elements with class card | .card { border: 1px solid } |
#hero | The element with id hero | #hero { height: 60vh } |
a:hover | A link under the pointer | a:hover { color: teal } |
input[type=email] | Inputs whose type is email | input[type=email] { border-radius: 4px } |
.card > h2 | h2 that is a direct child of .card | .card > h2 { margin-top: 0 } |
Classes are the workhorse. Reserve ids for anchors and for JavaScript hooks, because an id selector is hard to override later. Attribute and pseudo-class selectors let you style by state or property without adding extra classes.
CSS specificity in one table
Specificity is a weight, not a score you add up casually. Compare the counts left to right: ids first, then classes and attributes and pseudo-classes, then elements.
/* weight (id, class, element) */
p /* 0,0,1 */
.note /* 0,1,0 */
.card .title /* 0,2,0 */
#main .card .title /* 1,2,0 — the id wins */
a:hover /* 0,1,1 */
:where(.card) p /* 0,0,1 — :where adds nothing */Higher weight wins regardless of source order. When the weights tie, the later rule wins. Inline styles beat every selector, and !important beats inline styles and everything else — which is exactly why it becomes a maintenance problem.
Modern selectors and CSS specificity
Three additions to CSS have changed how people write selectors, and all three are widely supported now.
/* :is() matches any of a list, and takes the highest weight inside */
:is(h1, h2, h3) { line-height: 1.2 }
/* :where() matches but adds zero specificity — perfect for resets */
:where(ul, ol) { padding-inline-start: 1.25rem }
/* :has() styles a parent based on its children */
.field:has(input:invalid) { border-color: crimson }
form:has(input:focus) { box-shadow: 0 0 0 3px #99f6e4 }:is() and :where() both collapse a list of selectors into one. The difference is weight: :where() contributes nothing, so a base reset written with it is trivially overridable. And :has() is the first selector that lets you style backward — a field that contains an invalid input, a form that contains a focused input.
Pseudo-classes versus pseudo-elements
- Pseudo-classes describe a state or position:
:hover,:focus,:first-child,:nth-child(2n),:not(.active). - Pseudo-elements create a part of an element:
::before,::after,::marker,::placeholder,::selection.
li:nth-child(odd) { background: #f8fafc }
li:not(:last-child) { border-bottom: 1px solid #e2e8f0 }
::selection { background: #99f6e4 }
.external-link::after { content: " ↗"; }When styles do not apply: CSS specificity in practice
A rule that “does nothing” is almost always one of three things: another rule with higher specificity, a typo in the property or value, or a syntax error earlier in the file that discarded the whole rule. Open DevTools, select the element, and read the Styles panel — overridden declarations appear struck through with the winning rule shown above them. That panel answers nine out of ten CSS questions.
Next in this series: the CSS box model, which explains the space around every element. To learn CSS with a mentor on real layouts, Ampersand Academy teaches web development one-to-one.
Frequently asked questions
What is CSS specificity?
Specificity is the weight a selector carries when two rules target the same element. Inline styles beat ids, ids beat classes and attributes, and those beat element selectors. Equal weights are decided by source order.
What is the difference between is() and where()?
Both match any selector in a list, but :is() takes the specificity of its most specific argument while :where() adds zero specificity, which makes it ideal for base styles you want to override easily.
What does the has() selector do?
It styles an element based on what it contains, so you can change a field when it holds an invalid input or a form when one of its inputs has focus.
Should I use id selectors in CSS?
Rarely. An id selector outranks classes and is hard to override, so reserve ids for anchors and JavaScript hooks and style with classes instead.
Why is my CSS rule not applying?
Usually another rule with higher specificity is winning, or there is a typo or a syntax error that discarded the rule. The browser DevTools Styles panel shows which rule won.
Last updated on · Written by Dinesh Kumar R

