---
title: "03 — CSS — Styling a Tree of Competing Boxes"
uid: css-styling-and-the-cascade
tags: ["css", "cascade", "responsive", "box-model", "roadmap:frontend", "fundamentals", "specificity"]
excerpt: "CSS doesn't decorate HTML — it lays out a tree of boxes, and every rule competes in a tournament whose winner is decided by specificity and source order."
date: 2026-08-12T18:35:12+0000
source: https://www.aveshina.my.id/en/blog/css-styling-and-the-cascade
---

"The colors and fonts language, plus some centering" was my CSS model, and it fell apart the first time two rules fought over the same element. The frame that fixed it: **CSS is not just a paintbrush; it's a spatial architecture tool. It lays out a tree of boxes, and every rule competes in a tournament whose winner is decided by specificity + source order.** [2]

Once I could see CSS as two things at once — a _layout engine for boxes_ and a _resolution engine for conflicting rules_ (where the cascade does the fighting and inheritance is its fallback) — most of the stuff that felt arbitrary (why did my style not apply? why is this thing the wrong width?) turned into a question of _which engine_, not _what is going on_.

## First engine: everything is a box

The first thing I had to internalize is that CSS doesn't see tags or text. It sees boxes. Every element the browser renders — every <p>, <li>, <img> — generates a **box**, and that box has the same anatomy everywhere [3]:

```figure
<svg viewBox="0 0 560 260" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="The CSS box model. A content rectangle in the center labeled width / height, surrounded by a gold padding band, then a darker amber border band, then a pink margin band. Each band is labeled with its role: content, padding, border, margin.">
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">
    <!-- margin (outermost) -->
    <rect x="40" y="30" width="480" height="200" rx="10" fill="#fce7f3" stroke="#db2777" stroke-width="1.5"/>
    <text x="58" y="52" font-size="12" font-weight="700" fill="#500724">margin</text>
    <text x="58" y="68" font-size="10" fill="#500724">space outside the box · transparent · can collapse with neighbors</text>

    <!-- border -->
    <rect x="90" y="70" width="380" height="120" rx="8" fill="#fcd34d" stroke="#b45309" stroke-width="2"/>
    <text x="108" y="90" font-size="12" font-weight="700" fill="#422006">border</text>

    <!-- padding -->
    <rect x="110" y="90" width="340" height="80" rx="6" fill="#fef9c3" stroke="#ca8a04" stroke-width="1.2"/>
    <text x="126" y="108" font-size="11" font-weight="700" fill="#422006">padding</text>
    <text x="126" y="122" font-size="10" fill="#422006">space inside the box, between border and content</text>

    <!-- content -->
    <rect x="150" y="108" width="260" height="44" rx="4" fill="#e0e7ff" stroke="#6366f1" stroke-width="1.2"/>
    <text x="280" y="128" font-size="12" font-weight="700" fill="#1e1b4b" text-anchor="middle">content</text>
    <text x="280" y="144" font-size="10" fill="#475569" text-anchor="middle">width / height apply here</text>

    <text x="280" y="248" font-size="10.5" fill="#64748b" text-anchor="middle" font-style="italic">content → padding → border → margin, from the inside out</text>
  </g>
</svg>
```

The detail that bit me for years: by default, width sets the **content** size, not the box's total size. So width: 200px plus padding: 20px plus a 2px border makes the box render **244px** wide. The box ate the page. The fix everyone reaches for is box-sizing: border-box, which makes width mean "the whole visible box" — content shrinks to make the math work. I set it globally on every project now:

```
*,
*::before,
*::after {
  box-sizing: border-box;
}
```

Two more box facts worth keeping. **Margins collapse vertically** — two stacked block elements with margin: 20px each don't sit 40px apart; the margins merge into one 20px gap. Padding never collapses. And every box has a display that decides whether it stacks block (full width, new line), flows inline (sits in a text line), or behaves like the flexible/grid layouts below.

## Second engine: the cascade, and the tournament

The "C" in CSS is the part I had backwards. The **cascade** is the algorithm that decides, when multiple rules target the same element, which one wins [2]. The model that made it click: **every property on every element is a tournament**, and rules enter with a strength score called **specificity**.

The cascade resolves conflicts in this order, roughly:

1. **Origin and importance.** A browser default stylesheet loses to author CSS loses to !important (and user stylesheets slot in between). !important is a sledgehammer that opts out of normal ranking — I treat it as a code smell, not a tool.
2. **Specificity.** Among rules of the same importance, the more specific selector wins. Specificity is a three-number score (a, b, c) [4]:

- a — count of ID selectors (#nav).
- b — count of class, attribute, and pseudo-class selectors (.btn, [type=email], :hover).
- c — count of element and pseudo-element selectors (div, h1, ::before).
- (1, 0, 0) beats (0, 99, 99). One ID trumps any pile of classes. That's why adding a class "doesn't work" when an ID is already styling the element.

1. **Source order.** If origin and specificity tie, the rule written **later** in the stylesheet (or loaded later) wins.

```figure
<svg viewBox="0 0 600 220" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="A specificity tournament for one element. Three rules target the same h1. Rule one is element selector h1 with specificity 0-0-1. Rule two is a class .title with 0-1-0. Rule three is an ID #hero-title with 1-0-0. The ID rule wins and is crowned at the top, the class is second, the element rule is last.">
  <defs>
    <marker id="carrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
      <path d="M0,0 L10,5 L0,10 z" fill="#64748b"/>
    </marker>
  </defs>
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">
    <!-- target -->
    <rect x="220" y="14" width="160" height="34" rx="6" fill="#e0e7ff" stroke="#6366f1" stroke-width="1.5"/>
    <text x="300" y="35" font-size="12" font-weight="700" fill="#1e1b4b" text-anchor="middle">&lt;h1 id="hero-title" class="title"&gt;</text>

    <!-- element rule -->
    <rect x="30" y="90" width="180" height="60" rx="8" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
    <text x="120" y="112" font-size="13" font-family="ui-monospace, monospace" font-weight="700" fill="#052e16" text-anchor="middle">h1</text>
    <text x="120" y="130" font-size="11" fill="#052e16" text-anchor="middle">specificity  0-0-1</text>
    <text x="120" y="144" font-size="10" fill="#64748b" text-anchor="middle">weakest · loses</text>

    <!-- class rule -->
    <rect x="220" y="90" width="180" height="60" rx="8" fill="#fef9c3" stroke="#ca8a04" stroke-width="1.5"/>
    <text x="310" y="112" font-size="13" font-family="ui-monospace, monospace" font-weight="700" fill="#422006" text-anchor="middle">.title</text>
    <text x="310" y="130" font-size="11" fill="#422006" text-anchor="middle">specificity  0-1-0</text>
    <text x="310" y="144" font-size="10" fill="#64748b" text-anchor="middle">beats element · still loses</text>

    <!-- id rule -->
    <rect x="410" y="90" width="180" height="60" rx="8" fill="#fce7f3" stroke="#db2777" stroke-width="2"/>
    <text x="500" y="112" font-size="13" font-family="ui-monospace, monospace" font-weight="700" fill="#500724" text-anchor="middle">#hero-title</text>
    <text x="500" y="130" font-size="11" fill="#500724" text-anchor="middle">specificity  1-0-0</text>
    <text x="500" y="144" font-size="10" fill="#500724" text-anchor="middle">winner · crowned</text>

    <!-- arrows -->
    <path d="M120,90 L120,72 L258,48" fill="none" stroke="#64748b" stroke-width="1.3" marker-end="url(#carrow)"/>
    <path d="M310,90 L310,50" fill="none" stroke="#64748b" stroke-width="1.3" marker-end="url(#carrow)"/>
    <path d="M500,90 L500,72 L362,48" fill="none" stroke="#64748b" stroke-width="1.3" marker-end="url(#carrow)"/>

    <!-- crown -->
    <text x="500" y="180" font-size="20" text-anchor="middle">👑</text>
    <text x="300" y="200" font-size="10.5" fill="#64748b" text-anchor="middle" font-style="italic">same element — the ID wins outright, regardless of source order</text>
  </g>
</svg>
```

The diagnostic habit this gave me: when a style "isn't applying," I stop guessing and check **specificity**. DevTools shows the struck-through losing rules and the winning selector's score right next to them. Most "CSS is magic" moments are just a higher-specificity rule quietly winning.

## Inheritance: the cascade's last step

I used to think inheritance was a separate mechanism running beside the cascade. It isn't — it's the cascade's final fallback step. The cascade resolves a property by finding a declared value; when **no rule declares a value for that property** (no one entered the tournament), it falls back: some properties (color, font-family, font-size, line-height) take the parent's **computed** value — that's inheritance — while box-model properties (margin, padding, border, width) fall back to their **initial** value instead [5]. So an <h1> doesn't inherit its parent's border, thankfully — it just resets to the default.

The two keywords I use to reach into that fallback:

- inherit — force a normally-non-inherited property to take its parent's value.
- unset — reset to inherited if the property inherits, otherwise to the initial default.

This is also why I mostly style on a container and let values flow down, rather than restating font-family on every child. If I don't enter a rule into the tournament for a child, the cascade quietly falls back to the inherited value. The tree does the work.

## Layout: from floats to Flexbox and Grid

Historically, laying boxes out was the painful part — float, clear, clearfix hacks. Two modern systems replaced almost all of that [6]:

- **Flexbox** — one-dimensional layout. A row _or_ a column. Use it for navbars, button groups, centering a thing inside another thing (display: flex; align-items: center; justify-content: center). It distributes space along one axis.
- **Grid** — two-dimensional layout. Rows _and_ columns at the same time. Use it for page skeletons, card galleries, dashboards — anything with both axes.

The rule of thumb I landed on: **if I'm fighting alignment in one direction, it's Flexbox; if I'm placing things in a grid, it's Grid.** Both replace the float-era hacks entirely, and gap works in both, killing the margin-spaghetti that used to space items.

## Responsive: the unit is the screen

The last piece is that the boxes have to survive different screen sizes. Two ideas carry most of it:

- **Relative units.** rem scales with the root font size, % with the parent, vw/vh with the viewport. Hardcoded px everywhere breaks the moment the user zooms or the screen shrinks.
- **Media queries.** @media (min-width: 768px) { ... } lets rules apply only above a breakpoint — reflowing the grid from 3 columns to 1 on phones [7]. Combined with relative units, the same stylesheet adapts instead of being rewritten per device.

The mental shift was treating the viewport as the unit, not the desktop. Mobile-first — writing the small-screen styles first, then layering min-width overrides — is the cleanest way I've found to keep that in my head.

## How I use this

The payoff of the two-engine model is, again, diagnostic. When a style misbehaves I now ask _which engine_:

- Box is the wrong size / overlapping → **box model** — usually box-sizing or unexpected margin/padding.
- Style isn't applying at all → **the cascade** — DevTools, check specificity and source order.
- Value showed up on a child I didn't style → **inheritance (the cascade's fallback)** — no rule declared it, so it flowed down.
- Layout falls apart on mobile → **responsive** — wrong unit or missing query.

That single habit — naming the engine before reaching for a fix — is the whole reason these notes were worth writing down. CSS isn't one chaotic pile of properties. It's two engines running on the same tree of boxes, and most of debugging it is simply remembering that.

## References

[1] Mozilla, "HTML basics," MDN Web Docs, 2024. [Online]. Available: [https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web/HTML_basics](https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web/HTML_basics)

[2] Mozilla, "Introducing the CSS cascade," MDN Web Docs, 2024. [Online]. Available: [https://developer.mozilla.org/en-US/docs/Web/CSS/Cascade](https://developer.mozilla.org/en-US/docs/Web/CSS/Cascade)

[3] Mozilla, "CSS basic box model," MDN Web Docs, 2024. [Online]. Available: [https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_box_model](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_box_model)

[4] Mozilla, "Specificity," MDN Web Docs, 2024. [Online]. Available: [https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity](https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity)

[5] Mozilla, "CSS inheritance," MDN Web Docs, 2024. [Online]. Available: [https://developer.mozilla.org/en-US/docs/Web/CSS/Inheritance](https://developer.mozilla.org/en-US/docs/Web/CSS/Inheritance)

[6] Google, "Learn CSS," web.dev, 2023. [Online]. Available: [https://web.dev/learn/css/](https://web.dev/learn/css/)

[7] Mozilla, "Using media queries," MDN Web Docs, 2024. [Online]. Available: [https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries)

```quiz
Q: You set width: 200px on a box, then add padding: 20px and a 2px border. By default, how wide does the box render on screen?
- 200px
- 244px
- 240px
correct: 1
explain: Default box-sizing is content-box, so width applies to the content only. 200 + 20 + 20 (padding both sides) + 2 + 2 (border both sides) = 244px. Setting box-sizing: border-box makes width cover the whole visible box instead.

Q: Three rules target the same element: an element selector, a class, and an ID. Which wins?
- The element selector (written first)
- The class selector
- The ID selector
correct: 2
explain: Specificity is (1,0,0) for ID, (0,1,0) for class, (0,0,1) for element. One ID beats any number of classes, regardless of source order.

Q: A child element inherits its parent's font-family. When does inheritance actually kick in?
- Only when no rule declares font-family on the child — the cascade finds no winner
- Always, as long as the property is text-related
- Only after a media query matches
correct: 0
explain: Inheritance is the cascade's final fallback. It only kicks in when no rule declares a value for that property on the element. The cascade fights first; if nobody wins, the property falls back to the inherited (or initial) value.

Q: You need to lay out a row of nav links with even spacing and centered vertically. The right tool is…
- float and clearfix
- Flexbox
- a multi-column media query
correct: 1
explain: Flexbox is one-dimensional — a row or a column — and handles alignment and space distribution along one axis. It replaced float-based layouts for cases like this.

Q: Which property does NOT inherit down the tree by default?
- color
- font-family
- border
correct: 2
explain: Inherited properties are mostly text-related (color, font-family, font-size, line-height). Box-model properties like border, margin, padding, and width do not inherit.
```
