A rule
CSS is a list of rules. A rule is a selector — which elements — and a block of declarations — what to change. A declaration is a property, a colon, a value and a semicolon.
.card h2 {
color: #14161a;
font-size: 24px;
}
/* ^ ^ ^
| | value
| property
selector */
Whitespace does not matter. The last semicolon in a block is optional, but write it anyway — the next line you add will thank you.
Three places to put it
<link rel="stylesheet" href="/ui/site.css">
| Where | How | Use |
|---|---|---|
| An external file | <link rel="stylesheet" href="/style.css"> | Almost always. Cached, shared across pages, easy to find. |
| A style element | <style>…</style> in the head | A small page, or the handful of rules needed before the file arrives. |
| The style attribute | <div style="color:red"> | Values computed by JavaScript. Nothing else — it beats your stylesheet and cannot be reused. |
A stylesheet blocks rendering until it arrives. That sounds bad and is deliberate: without it the reader sees a flash of unstyled text. Keep it small and let it block.
What the browser does
Changing a colour repaints. Changing a width re-lays-out and repaints, which is far more work. Changing a transform or an opacity can often skip straight to compositing — which is why those two are the properties to animate.
- Parse. The HTML becomes the DOM; the CSS becomes a set of rules.
- Style. For every element, work out the value of every property — the cascade, inheritance and defaults all resolve here.
- Layout. Work out the size and position of every box. Also called reflow.
- Paint. Fill in pixels: colours, text, shadows, images.
- Composite. Put the painted layers together on screen.
Comments
There is no // in CSS. A line beginning with // is an invalid declaration, and the browser drops it — along with, sometimes, the rest of the block.
/* This is a comment.
It can run over several lines. */
.card { padding: 16px; } /* and it can sit at the end of a line */
Invalid values are ignored, one at a time
If the browser does not understand a declaration, it discards that declaration and carries on. The rest of the rule still applies. This is the mechanism that makes progressive enhancement work: write the fallback first, then the better value.
.panel {
background: #101114; /* everyone */
background: color-mix(in oklab, #101114, white 4%); /* where supported */
}
Asking whether something is supported
.grid {
display: flex;
flex-wrap: wrap;
}
@supports (display: grid) {
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
}
The default stylesheet
Before any of your CSS, the browser applies its own: h1 is large and bold, a is blue and underlined, body has a margin, lists have bullets. Those are real rules, and they are why an unstyled page still reads.
A reset or normalize stylesheet levels the small differences between browsers. A modern minimal one is only a few lines.
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; }
img, video, svg { display: block; max-width: 100%; }
input, button, textarea, select { font: inherit; }
h1, h2, h3, p { overflow-wrap: break-word; }
font: inherit on form controls is the one people forget: without it, an <input> uses the operating system font at 13px and ignores everything you set on body.
Where to look when it does not work
- Open the Elements panel, select the element, read the Styles pane. Every rule that matched is listed, in order.
- A property with a line through it lost to another rule — the winner is above it.
- The Computed pane shows the final value and which rule produced it.
- If your rule is not listed at all, the selector did not match. That is a different bug from losing the cascade.