How Specificity Works

When two CSS rules target the same element, the one with higher specificity wins, regardless of order in the stylesheet. Specificity is calculated as a score with three components:

Selector TypeScoreExample
Inline style1,0,0,0style="color: red"
ID selector0,1,0,0#header
Class, attribute, pseudo-class0,0,1,0.active, [type], :hover
Element, pseudo-element0,0,0,1div, p, ::before
* Universal selector0,0,0,0*

Calculating Specificity

p               { }  /* 0,0,0,1 */
.text           { }  /* 0,0,1,0 */
p.text          { }  /* 0,0,1,1 — wins over .text alone */
#main p         { }  /* 0,1,0,1 */
#main .text     { }  /* 0,1,1,0 — wins over #main p */
style=""           /* 1,0,0,0 — always wins except !important */

Solutions (In Order of Preference)

// 1. Make your selector more specific (match the existing selector structure)
// If overriding: .nav .link { color: red; }
// Write: .nav .link:hover { color: blue; } — adds :hover

// 2. Add a parent class
.dark-theme .btn { background: #333; }  /* more specific than .btn */

// 3. Use :is() or :where() for specificity tricks
:is(#page, main) .btn { }  /* takes specificity of highest selector */
:where(.nav) .btn { }      /* always 0 specificity — easy to override */

// 4. Use CSS custom properties as a bridge
.btn { background: var(--btn-bg, #6366f1); }
.dark .btn { --btn-bg: #333; }  /* overrides without fighting specificity */

About !important

/* !important overrides all specificity — use as last resort */
.btn { color: red !important; }

/* Problems with !important */
/* 1. Creates specificity debt — need !important to override !important */
/* 2. Hard to debug */
/* 3. Breaks the cascade */

/* The only legitimate uses: */
/* - Overriding third-party library styles you can't change */
/* - Utility classes that must always win (like display: none) */
.hidden { display: none !important; }  /* intentional — ensures hidden stays hidden */