How CSS Specificity Resolves Style Conflicts

CSS specificity is a weight-based scoring system that browsers use to determine which style declarations apply to an element when multiple conflicting rules are present. By assigning different numerical weights to various types of selectors, the browser calculates a specificity value for each rule; the declaration associated with the highest score takes precedence and gets rendered. This article explains how specificity values are calculated, how ties are broken by source order, and how special modifiers like !important alter the normal cascade.

The Specificity Hierarchy

When conflicting declarations target the same CSS property on an element, the browser evaluates selectors across four distinct tiers of priority:

  1. Inline Styles: Styles applied directly to an HTML element via the style attribute have the highest standard specificity.
  2. ID Selectors: Selectors that target elements by ID (e.g., #header) carry high weight and easily override classes and elements.
  3. Class, Attribute, and Pseudo-Class Selectors: This category includes class selectors (e.g., .button), attribute selectors (e.g., [type="submit"]), and pseudo-classes (e.g., :hover, :focus).
  4. Element and Pseudo-Element Selectors: The lowest priority tier belongs to raw HTML tag selectors (e.g., div, p, h1) and pseudo-elements (e.g., ::before, ::after).

Universal selectors (*), combinators (+, >, ~, space), and the :where() pseudo-class contribute nothing (0-0-0) to specificity.

Calculating Specificity Values

Specificity is typically calculated as a four-part tuple: (Inline, IDs, Classes, Elements).

Instead of adding these values up as standard base-10 numbers, browsers compare them from left to right, matching values at the highest tier first:

  • style="color: red;" has a value of (1, 0, 0, 0).
  • #nav has a value of (0, 1, 0, 0).
  • .menu .item has a value of (0, 0, 2, 0).
  • nav ul li a has a value of (0, 0, 0, 4).

Because comparison proceeds from left to right, a single ID selector (0, 1, 0, 0) will always override any combination of classes and element tags, regardless of how many classes are chained together (e.g., (0, 0, 15, 0) still loses to (0, 1, 0, 0)).

Resolving Ties: The Cascade and Source Order

If two conflicting CSS rules have the exact same specificity calculation, the browser resolves the conflict using the rule of source order: the declaration defined last in the stylesheet wins.

/* Specificity: (0, 0, 1, 0) */
.alert {
  color: blue;
}

/* Specificity: (0, 0, 1, 0) */
.alert {
  color: red; /* This declaration wins because it appears later */
}

This principle also applies across multiple linked stylesheets; the stylesheet loaded later in the document's <head> will override identical-specificity rules from earlier stylesheets.

The !important Rule

Appending !important to a CSS declaration removes it from standard specificity calculations entirely. An !important rule overrides all normal declarations, including inline styles. When two conflicting rules both feature !important, the browser falls back to standard specificity and source order to resolve the tie between those two specific rules. Due to its disruptive effect on the stylesheet cascade, !important is typically reserved for critical utility overrides.

Modern Specificity Controls

Modern CSS offers dedicated pseudo-classes to manage specificity proactively:

  • :is() and :not(): The specificity of these pseudo-classes adopts the specificity of the most specific selector in its argument list.
  • :where(): Always maintains a specificity of (0, 0, 0), regardless of the selectors passed inside it, making it ideal for creating easily overridable default styles.
  • @layer: CSS Cascade Layers allow developers to establish explicit priority tiers regardless of selector specificity. Rules in a higher layer always override rules in a lower layer, with standard specificity only evaluated within each individual layer.