EverProduct
Frontend

Stage 01 · Between the Code and the Screen

The Courtroom: Why Your CSS "Doesn't Work"

You wrote the rule, the browser ignored it, you added !important and moved on. It wasn't ignored — it lost a case decided by a procedure you can learn in an afternoon.

You set color: red on the element. Nothing happens. You add another class. Still nothing. You write !important, the text turns red, and you move on with a faint sense that CSS is arbitrary.

It isn't. Every property on every element had several rules claiming it, and the browser resolved the conflict by a fixed, published procedure. Your rule didn't fail — it lost, and you never found out to whom.

This is the single largest source of wasted hours for people learning frontend, and it's caused by one misconception: that CSS is a list of commands. It isn't. CSS is a set of claims, and the cascade is the court that settles them.

The order of judgement

For each property of each element, the browser sorts every claim by this procedure and takes the winner:

  1. Origin and importance. Your stylesheet beats the browser's defaults. !important inverts this whole level rather than adding power to it — which is why an !important in a library's stylesheet can only be beaten by another !important, and why the escalation never ends.
  2. Specificity. A counted score, described below.
  3. Source order. All else equal, the last rule wins. Which is the entire reason the order of your files matters, and why your override works when you move it below the thing it's overriding.

Two things absent from that list are worth noting, because beginners assume both: how many selectors you wrote isn't a step, and how recently you saved the file isn't either.

Specificity is a triple, not a number

Specificity is counted as three numbers, compared left to right like a version number:

  • IDs#header
  • Classes, attributes, pseudo-classes.card, [disabled], :hover
  • Elements and pseudo-elementsdiv, a, ::before

So .card .title scores 0-2-0, and #main p scores 1-0-1. The second wins, despite having fewer classes, because the leftmost column decides first and no quantity of classes ever climbs into it. That's the rule people intuitively get wrong: a hundred classes still lose to one ID.

Two consequences you'll live with daily. An ID in a selector is a promise you'll never be able to override it politely, which is why most codebases style with classes only. And !important sits above the whole scale — it's not a strong argument, it's leaving the courtroom and legislating. Occasionally justified; almost always a debt someone pays later, usually with a second !important.

If you meet @layer in a modern codebase, it's the grown-up answer to all of this: explicit named layers, ordered by you, where a whole layer's claims are weighed before specificity is even considered. It exists precisely because specificity wars were the norm.

Inheritance: what falls through and what doesn't

Set color on the body and the whole page turns that colour. Set border on the body and nothing inside it gets a border.

That's not inconsistency. Properties about text — colour, font family, size, line height, alignment — inherit down the tree, because that's almost always what you want. Properties about boxes — border, padding, margin, background, width — do not, because inheriting a border would be absurd.

Two keywords let you steer it explicitly: inherit takes the parent's value on purpose, initial resets to the property's built-in default. And inheritance is the reason form controls look wrong until you tell them otherwise — input, select and button don't inherit the page's font by default, which is why nearly every stylesheet on earth contains a line that makes them.

The box model, and the line everyone writes

Every element is a rectangle built in four layers: content in the middle, padding around it inside the frame, border as the frame, margin as the empty space outside it.

The historical trap: by default, width sets the width of the content only. Give a box 300 pixels of width, 20 of padding and 1 of border, and it occupies 342. Which is why nearly every project starts by setting box-sizing: border-box on everything — after that, width means the width of the whole visible box, the way you assumed it did.

Margins bring their own oddity: vertical margins between adjacent blocks collapse into one, taking the larger of the two rather than adding up. 20 below one paragraph and 30 above the next produces 30, not 50. This surprises everyone exactly once, and it disappears entirely in flex and grid containers, where margins don't collapse.

The five reasons a rule "doesn't apply"

When a style stubbornly refuses to take effect, it's almost always one of these, and going through them in order is faster than any amount of trying things:

  1. Something more specific won. Devtools shows the losing rule struck through — that's the whole answer, visible in two seconds.
  2. Source order. Same specificity, and something later said otherwise.
  3. The selector doesn't match what you think. A typo, the wrong nesting, an element rendered by JavaScript that isn't there yet.
  4. The property doesn't apply to that element. width on an inline element does nothing; justify-content does nothing outside a flex or grid container; z-index does nothing on a statically positioned element. Half of "CSS is broken" is a property applied to a context that ignores it.
  5. The value is invalid. One typo in a value and the browser silently discards that single declaration. No error, no warning — devtools marks it, and nothing else will.

Notice that not one of these is solved by adding !important. Adding it is what you do when you've decided not to find out which of the five it was.

CSS never "doesn't work." It does exactly what the rules say. You just weren't in the room when the case was decided.

Learn four mechanisms, not four hundred recipes

CSS has a reputation for being learnable only by accumulating tricks — and that reputation is produced by how it's usually studied: search a problem, copy a snippet, move on. It works today and teaches nothing, because the snippet is a memorised snapshot from one angle, and next week's problem is at a different angle.

The How to Learn sphere's answer applies almost too neatly here. Understanding means being able to change scale on the same object: zoom out to what is CSS doing at all (assigning one value per property per element), zoom in to how did this element get this value (the four mechanisms — cascade, specificity, inheritance, the box model). Every property you'll ever meet plugs into that frame. Learn the frame and new properties cost minutes; skip it and each one is a separate fact to memorise, forever.

It also gives you something rare in learning: an instant, honest external judge. You form a hypothesis about why the element looks like that, check it in the inspector, and are proved right or wrong in five seconds. That's deliberate practice with a feedback loop tighter than almost any other subject offers — but only if you make the prediction first. Change things at random until it looks right and you get the same result with none of the learning.

In practice

Inspect before you edit. When something's off, open devtools, select the element, and read the Styles panel top to bottom: what applied, what's struck out, what the computed value ended up as. Doing this for a week rewires how you think about CSS more than any tutorial.

Predict, then check. Before opening the inspector, say why you think it's happening. Being wrong with a stated prediction is worth several times being told the answer.

Ban !important for a month. Not forever — for a month, while you're learning. Every time you want it, find out which of the five reasons it actually was. This one constraint teaches specificity faster than reading about specificity.

Style with classes only. No IDs in selectors, no element selectors deep in components. Keep your specificity flat and most cascade fights simply never happen.

Recreate a small component from a screenshot, then open the original and compare property by property. Copying then rebuilding from memory is the technique from the How to Learn sphere in its cheapest form, and CSS is unusually well suited to it.

Say the box model out loud while looking at devtools' box diagram: content, padding, border, margin. Three days later, say it again without looking. It's four words, and it's the thing you'll use every hour.

Check yourself

Close the article and answer in your own words:

  1. What are the three stages of the cascade, in order?
  2. Why does #main p beat .card .title .text?
  3. What does !important actually do to the procedure, and why is it a debt?
  4. Which kinds of property inherit and which don't — and what's the logic behind the split?
  5. What does box-sizing: border-box change, and why does nearly every project set it?
  6. List the five reasons a rule might not apply.
  7. Why is "add !important" the answer of someone who chose not to diagnose?

In short

  • CSS is not a list of commands but a set of claims; the cascade is the procedure that decides which claim wins for each property of each element.
  • The order is origin and importance, then specificity, then source order. Number of selectors and file recency are not steps.
  • Specificity is a triple — IDs, then classes/attributes/pseudo-classes, then elements — compared left to right, so a hundred classes lose to one ID.
  • Text-related properties inherit, box-related ones don't; inherit and initial steer it explicitly.
  • The box model is content, padding, border, margin; border-box makes width mean what you assumed, and vertical margins between blocks collapse to the larger one.
  • A rule "not working" is one of five things: specificity, order, a non-matching selector, a property that doesn't apply to that context, or an invalid value.
  • !important is not a fix but a decision not to diagnose, and it escalates.
  • Learn four mechanisms instead of four hundred recipes, and use the inspector as a five-second feedback loop — after stating a prediction, not instead of one.