Part 1: Selectors, Specificity, and the Cascade

Introduction

CSS (Cascading Style Sheets) is what I pick up immediately after writing HTML. The two are inseparable in practice β€” the markup gives structure, CSS gives it visual form. But CSS has subtleties that take time to internalise. The cascade, specificity, and inheritance explain why a style you wrote seems to have no effect, or why removing one rule breaks three unrelated things. Once those mechanics click, CSS stops being frustrating and becomes predictable.

This part covers how CSS works at the engine level, every selector type I use day-to-day, and how the browser resolves conflicts between competing rules.


How to Apply CSS

There are three ways to attach CSS to an HTML document.

External Stylesheet (preferred)

<link rel="stylesheet" href="styles.css" />

The stylesheet is a separate .css file, cached by the browser across pages, and easy to maintain. This is what I use for every project.

<style> Element

<head>
  <style>
    body {
      font-family: sans-serif;
    }
  </style>
</head>

Useful for page-specific overrides or critical above-the-fold styles to avoid a render-blocking request.

Inline Styles (avoid for general use)

Inline styles have the highest specificity short of !important, making them difficult to override. They are appropriate when CSS is generated dynamically (e.g., a JavaScript-driven colour value), not for general styling.


CSS Rule Anatomy

  • Selector β€” targets the element(s) to style

  • Declaration block β€” { ... } contains one or more declarations

  • Declaration β€” a property: value pair ending with ;


Selectors

Type Selector

Targets all elements of a given tag name.

Class Selector

Targets elements with a specific class attribute value. The most common selector in everyday CSS.

A single element can have multiple classes separated by spaces.

ID Selector

Targets a single element with a specific id.

I use ID selectors sparingly in CSS β€” their specificity is much higher than class selectors, making them hard to override. IDs are better reserved for JavaScript targeting and fragment links.

Universal Selector

Matches every element.

The most common use: applying box-sizing: border-box globally. In isolation it is fine; avoid using it for broad style rules as it matches everything in the document.

Attribute Selector

Targets elements based on the presence or value of attributes.

Pseudo-Class Selectors

Pseudo-classes target elements in a specific state.

Pseudo-Element Selectors

Pseudo-elements create virtual sub-elements for styling.

The double-colon :: is the CSS3 syntax for pseudo-elements. Single colon : works too for historical reasons, but :: is the modern standard.

Combinator Selectors

Combinators select elements based on their relationship to other elements.


Specificity

When multiple rules target the same element and the same property, specificity determines which value wins.

How Specificity is Calculated

Specificity is a three-number score (A, B, C):

Selector type
Contributes to

Inline style (style="...")

A

ID (#id)

A

Class (.class), pseudo-class (:hover), attribute ([type])

B

Type (p, h2), pseudo-element (::before)

C

Universal (*), combinators ( , >, +, ~)

Nothing

Higher A beats lower A regardless of B and C; A ties compare B; B ties compare C.

Examples

For a <p class="warning">, the text is orange because .warning has higher specificity.

!important

!important overrides the entire specificity system. It should be used as a last resort β€” browser resets, utility classes, or third-party overrides. Once !important appears in a codebase, the only way to override it is with another !important, creating an escalating mess.


The Cascade

The cascade is the algorithm the browser uses to determine which value applies when multiple declarations target the same property on the same element.

The cascade considers three factors, in priority order:

1. Origin and Importance

Origin

Normal

!important

User agent (browser defaults)

Lowest

High

User styles (browser settings)

Medium

Higher

Author styles (your CSS)

High

Highest

Your CSS overrides browser defaults, which is why <button> looks different once you style it.

2. Specificity

Higher specificity wins when origins are the same and neither uses !important.

3. Order of Appearance

When two rules have the same origin and specificity, the last one wins.

This is why the order of <link> tags and @import declarations matters.


Inheritance

Some CSS properties inherit their value from a parent element automatically; others do not.

Inherited by default (typography-related): color, font-family, font-size, font-weight, font-style, line-height, letter-spacing, text-align, text-transform, visibility, cursor

Not inherited by default (box/layout-related): margin, padding, border, background, width, height, display, position, overflow

Controlling Inheritance


CSS Reset and Normalisation

Browsers ship with their own default styles (the user-agent stylesheet). <h1> has a large font size and margin. <ul> has left padding and bullets. <button> has a border and background. These defaults differ slightly between browsers.

My Minimal Reset

This is the reset I paste into every project's styles.css:

Key decisions here:

  • box-sizing: border-box β€” padding and border are included in element width/height, not added to it. This is the model that matches how most developers think about sizing.

  • margin: 0; padding: 0 β€” removes browser spacing so I start from a known baseline.

  • font: inherit on form elements β€” browsers default form elements to their own font, which often does not match the page font.

  • max-width: 100% on media β€” prevents images from overflowing their container.


The :root Selector and Global Variables

The :root pseudo-class targets the document root element (<html>). It is where I define CSS custom properties (variables) that apply across the entire page.

Custom properties are covered in depth in Part 5, but defining them at :root is a pattern worth establishing from the start.


Summary

Concept
Key Point

Type selector

Targets all elements of that tag

Class selector

Most common β€” reusable, composable

ID selector

High specificity β€” use sparingly in CSS

Attribute selector

Target by attribute presence or value

Pseudo-class

Element state (:hover, :focus, :nth-child)

Pseudo-element

Virtual sub-elements (::before, ::after)

Combinators

Structural relationships (descendant, child, sibling)

Specificity

(id, class/pseudo-class/attr, type) β€” higher wins

!important

Overrides specificity β€” use as last resort

Cascade order

Origin β†’ specificity β†’ source order

Inheritance

Typography inherits; box model does not

box-sizing: border-box

Apply globally β€” makes sizing intuitive


Up Next

Part 2 covers the box model, display modes, and positioning β€” the foundation of how elements take up and share space on a page.

Last updated