Arkendia Classrooms
Lesson 9 of 21 · 18 min

Selectors, specificity, the cascade and inheritance

Have you ever written a CSS rule that simply refused to apply? Almost always, another rule won the competition. In this lesson you will learn how to target elements precisely, how the browser decides between conflicting rules, and why some properties pass from parents to children. These mechanisms are the heart of CSS: once you grasp them, CSS stops feeling random.

Selecting elements

A selector describes which elements a rule applies to. Here are the ones you will use daily.

Basic selectors

p { }              /* type selector: all p elements */
.card { }          /* class selector */
#main-title { }    /* id selector (unique) */
* { }              /* universal selector: everything */

Classes are the workhorse. Give meaningful names that describe the role, for example project-card, not the look, such as big-blue-box. If the design changes, the name remains correct.

Combining selectors

article p { }        /* descendant: any p inside an article */
article > p { }      /* child: p directly inside an article */
h2 + p { }           /* adjacent sibling: the p right after an h2 */
h2 ~ p { }           /* general sibling: all p after an h2 */
p.intro { }          /* a p that also has the class intro (no space) */
.card.featured { }   /* an element with both classes */
h1, h2 { }           /* either h1 or h2 */

Notice the difference between article p (with a space, a descendant) and p.intro (no space, the same element with both conditions). A stray space is a classic cause of rules that do nothing.

Attribute selectors

a[href^="https"] { }        /* links whose href starts with https */
a[target="_blank"] { }      /* links that open in a new tab */
input[type="email"] { }     /* only email fields */

Pseudo-classes

A pseudo-class targets an element in a particular state or position, with a colon:

a:hover { }              /* mouse over */
a:focus-visible { }      /* keyboard focus */
input:invalid { }        /* a field that fails validation */
li:first-child { }       /* the first li among its siblings */
li:last-child { }        /* the last one */
li:nth-child(2n) { }     /* every second li */
a:not(.button) { }       /* links without the class button */

Pseudo-elements

A pseudo-element, with a double colon, targets a part of an element or generates one:

p::first-line { }        /* the first line of a paragraph */
.quote::before {
  content: "“";          /* adds a character before the element */
}

Generated content is for decoration only. Important information should always be in the HTML itself.

When rules collide: the cascade

Several rules can target the same element and set the same property. The browser must pick one. The algorithm is called the cascade, which gives CSS its name. In simplified terms, it considers, in order:

  1. Origin and importance: browser defaults are weakest, then the styles of the site, and a declaration marked !important beats normal ones.
  2. Specificity: a more precise selector wins over a more general one.
  3. Order: if everything else is equal, the rule written last wins.

Specificity

Specificity is like a score that measures how precise a selector is. You can think of three columns, compared from left to right:

SelectorIDsClasses, attributes, pseudo-classesTypes, pseudo-elements
p001
.intro010
p.intro011
nav a:hover012
#logo100

A single ID outweighs any number of classes, and a class outweighs any number of type selectors. The columns are compared in order, not added into one number. The universal selector * counts for nothing, and inline style attributes beat all of these.

An example:

p { color: black; }          /* 0-0-1 */
.intro { color: blue; }      /* 0-1-0: wins over p */
p.intro { color: green; }    /* 0-1-1: wins over both */

A paragraph with class="intro" is green. Reordering these rules changes nothing, since specificity is decided before order.

Keeping specificity low

The professional habit is to keep specificity flat and low: mostly single-class selectors. That way, rules compete on a level field and order decides, which is predictable. Avoid IDs for styling and avoid long chains like body main section article p.intro: they are hard to override later.

Warning. !important forces a declaration to win, so it is tempting when a rule does not apply. But it creates an arms race: to override it you need another !important. Use it only as a last resort, and never as a fix for a specificity problem you do not understand.

Order matters

When two selectors have the same specificity, the later one wins. This is why you typically write general rules first (the page-wide defaults) and specific rules after (components and variations). The same goes for several stylesheets: the one linked last wins ties.

Inheritance

Some properties pass from parent to child automatically. If you set color on body, every element inside uses it, unless it has its own rule. That is why styling body with a font family affects the whole page.

Properties related to text typically inherit: color, font-family, font-size, line-height, text-align. Properties related to the box generally do not: margin, padding, border, background. It would be odd if a border around a parent were copied around each child.

You can control this explicitly with special values:

  • inherit: take the parent’s value.
  • initial: reset to the CSS default value for the property.
  • unset: inherit if the property normally inherits, otherwise reset.
a {
  color: inherit;      /* links use the text color of their parent */
}

Debugging with the developer tools

Select an element in the Elements panel. In the Styles pane, rules are listed from the strongest to the weakest. Declarations that lost the competition appear crossed out. Hovering over a selector shows its specificity in some browsers. A “Computed” tab shows the final value of every property after the cascade. When a rule does not apply, this is where you will find the winner.

Tip. When a rule does not apply, ask three questions in order: (1) Does my selector actually match the element? (2) Is another rule more specific? (3) Is another rule equal but later in the file? These cover nearly all cases.

Organizing selectors on your portfolio

Give your menu and your project cards classes, and use them:

<nav class="site-nav" aria-label="Main">
  <ul>
    <li><a href="index.html" class="active">Home</a></li>
    <li><a href="about.html">About</a></li>
  </ul>
</nav>
.site-nav a {
  color: #334155;
  text-decoration: none;
}
.site-nav a:hover,
.site-nav a.active {
  color: #0f766e;
  text-decoration: underline;
}

Your turn

Solve this small puzzle without running the code first. Given this HTML and CSS, what color is each paragraph?

<section class="featured">
  <p>Paragraph A</p>
  <p class="note">Paragraph B</p>
  <p id="special" class="note">Paragraph C</p>
</section>
p { color: black; }
.featured p { color: green; }
.note { color: blue; }
#special { color: red; }

Then test it in your browser, and apply the idea to your own site: give every project title a class and style it.

See a solution

Paragraph A is green: .featured p (0-1-1) beats p (0-0-1).

Paragraph B is also green: .featured p has specificity 0-1-1 and .note only 0-1-0. The extra type selector wins, even though .note comes later.

Paragraph C is red: the ID selector has the highest specificity.

The lesson: a selector that looks more precise can unintentionally override your class. This is a strong argument for keeping selectors short and consistent.

Key takeaways

  • Select with types, classes, IDs, combinators (space, >, +, ~), attributes, pseudo-classes and pseudo-elements.
  • The cascade decides by importance, then specificity, then order of appearance.
  • Specificity compares IDs first, then classes, then types; keep it low and flat with single classes.
  • Text properties usually inherit from the parent; box properties do not. inherit, initial and unset control this.
  • Avoid !important; use the developer tools to see which rule wins.
Advertisement
Log in to save your progress. Log in