CSSHow CSS Works

How CSS Works

Before you write a single line of CSS it's worth understanding what actually happens when the browser reads your stylesheet. This isn't dry theory — knowing this model helps you debug mysterious problems, understand why some styles seem to be ignored, and write CSS that does exactly what you intend.

The anatomy of a CSS rule

Every CSS rule has two parts: a selector that identifies which HTML elements to target, and a declaration block that contains one or more property–value pairs.

CSS
/* This is a CSS rule */
h1 {              /* ← selector: targets every <h1> element */
  color: navy;    /* ← declaration: property "color", value "navy" */
  font-size: 2rem;/* ← another declaration */
}

/* Anatomy labels:
   selector  { property: value; property: value; }
                └─ declaration block ─────────────┘ */
The semicolon after each declaration is required — the last one in a block is technically optional, but always include it to avoid subtle bugs when adding more declarations
Every declaration must end with a semicolon (`;`). The browser uses it to know where one declaration ends and the next begins. Forgetting one causes every subsequent declaration in that block to be silently ignored — a frustrating bug to track down. The safe habit is to always include the semicolon, including on the last line.
How the browser parses CSS

When you load a page, the browser does the following in roughly this order:

Text
1. Parse the HTML → build the DOM (Document Object Model)
   A tree of every element on the page

2. Load linked resources
   Images, fonts, and — crucially — CSS files

3. Parse the CSS → build the CSSOM (CSS Object Model)
   Another tree, but for style rules

4. Combine DOM + CSSOM → the Render Tree
   Only elements that will actually be visible are included
   (display:none elements are excluded)

5. Layout (Reflow)
   Calculate the exact size and position of each element

6. Paint
   Fill in the pixels — colours, borders, shadows, text

7. Composite
   Layer the painted tiles together (for GPU acceleration)

Then: display the finished frame to the screen
The DOM and CSSOM are built in parallel, but rendering can't begin until both are complete — a large stylesheet blocks the first paint
This is why you link stylesheets in the `<head>` rather than at the bottom of the page. The browser needs the CSSOM before it can render anything — a stylesheet in the `<body>` could cause the page to flash unstyled content. It's also why **critical CSS** (inlining the styles needed for the visible viewport) is an important performance technique — it lets the browser paint the first screen without waiting for the full stylesheet to load.
Selectors and the matching process

The browser reads selectors right to left. For a selector like nav ul li a, it first finds all <a> elements on the page, then checks if each is inside an <li>, then checks if that <li> is inside a <ul>, then checks if that <ul> is inside a <nav>. This direction matters for performance: a very broad rightmost element like a matches thousands of elements immediately, then most are filtered out. Keeping the rightmost part specific is why unnecessarily deep selectors are slow.

The cascade in one sentence

Once the browser has found all the rules that match an element, it needs to decide which ones win. The cascade is the algorithm that resolves these conflicts. In brief:

  • Origin — browser default styles lose to author styles (your CSS), which lose to user-defined styles.

  • Specificity — a more specific selector (e.g. #header p) beats a less specific one (e.g. p), regardless of order.

  • Order — when specificity is equal, whichever rule appears later in the stylesheet wins.

  • Importance!important overrides everything in the current origin (use sparingly).

This guide has a full section on the cascade — it's one of the most important concepts in CSS.

Inheritance

Some CSS properties inherit — they automatically pass their value down from a parent element to its children. Others don't. Knowing which is which saves a lot of repetition.

Inherits by default

Does NOT inherit by default

color

background-color

font-family, font-size, font-weight

border, padding, margin

line-height, letter-spacing

width, height

text-align, text-decoration

display, position

cursor, visibility

box-shadow, transform

CSS
body {
  font-family: system-ui, sans-serif;
  color: #333;
}
/* Every element on the page — headings, paragraphs, links — inherits
   these values without you having to re-declare them. This is how CSS
   lets you set a "base" style once. */
How values are computed

When you write font-size: 1.5em, the browser doesn't store "1.5em" — it converts every value to an absolute computed value at render time. That 1.5em becomes something like 24px based on the parent's font size. This resolved value is what you see in DevTools under "Computed" styles. Understanding the difference between specified values (what you wrote), computed values (what the cascade resolves to), and used values (what the browser actually lays out with) helps enormously when debugging why a style doesn't look like you expect.

CSS is applied to every matching element globally — there is no automatic scoping between components the way there is in React or Vue
When you write `p { color: red }`, **every** `<p>` on the page turns red — including those deep inside components you didn't think about. This global nature of CSS is one of its trickiest aspects when building large applications. Solutions include naming conventions (BEM), CSS Modules (which scope styles to a file), utility classes (Tailwind), or CSS custom properties for theming. For now, just be aware: unlike JavaScript where `const x = 1` is scoped to its function, CSS rules are always global unless you take deliberate steps to scope them.
Next
A brief look at where CSS came from and why it works the way it does: [CSS History & Evolution](/css/css-history).