CSS & Tailwind
CSS (Cascading Style Sheets) is a declarative language for describing how a document is presented: colour, size, spacing, position, typography, and motion. HTML says what a thing is; CSS says how it looks and where it sits.
CSS is not a programming language. There are no loops and no imperative statements. You write rules that say “elements matching this pattern should have these property values”, and the browser figures out the rest — which rule wins, what the computed value is, and how the page lays out.
What CSS actually does
Section titled “What CSS actually does”The browser turns your HTML into a DOM tree (a tree of element nodes). It then:
- Parses every stylesheet into a list of rules.
- For each element, finds every declaration that could apply to it, and resolves conflicts using the cascade and specificity — this produces the computed style.
- Builds a box for each visible element and runs layout to decide its size and position.
- Paints those boxes, in an order determined by stacking rules.
Almost every CSS problem is a problem in one of those four steps: the rule didn’t match, the wrong rule won, the box model wasn’t what you assumed, or the paint order was not what you expected.
How to include CSS
Section titled “How to include CSS”There are three places CSS can live. Only one of them is a good default.
External stylesheet (use this)
Section titled “External stylesheet (use this)”<!doctype html><html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="stylesheet" href="/styles.css" /> </head> <body> <h1 class="title">Hello</h1> </body></html>.title { color: rebeccapurple; font-size: 2rem;}A separate file is cacheable, reusable across pages, and keeps content separate from presentation. <link rel="stylesheet"> is render-blocking by design — the browser will not paint until it has the CSS, which avoids a flash of unstyled content.
Internal (a <style> block)
Section titled “Internal (a <style> block)”<head> <style> body { margin: 0; font-family: system-ui, sans-serif; } </style></head>Useful for a single-file demo, for critical above-the-fold CSS inlined to avoid an extra request, or for email templates. Not for application code.
Inline (a style attribute)
Section titled “Inline (a style attribute)”<p style="color: red;">Danger</p>Applies to exactly one element, cannot express :hover or media queries, and has very high priority in the cascade, so it is hard to override. Legitimate uses are narrow: values computed at runtime by JavaScript (a progress bar width, a drag position), and setting a custom property from a template.
@import
Section titled “@import”@import "typography.css";Valid, but each @import is discovered only after the importing file is downloaded and parsed, so imports serialise network requests. In a build pipeline (Vite, PostCSS, Sass) @import is resolved at build time and is fine — Tailwind v4 relies on exactly this. In a raw browser stylesheet, prefer multiple <link> tags.
Anatomy of a rule
Section titled “Anatomy of a rule”/* selector */ /* declaration block */h1, .title { color: #222; /* property: value; -> one declaration */ margin-block-end: 1rem;}- Selector — the pattern that decides which elements the block applies to.
h1, .titleis a selector list: the block applies to anything matching either. - Declaration block — everything between the braces.
- Declaration — a
property: valuepair, terminated by a semicolon. The last semicolon is optional but always write it. - Comment —
/* ... */. There is no//line comment in CSS.
An invalid declaration is discarded silently and the rest of the block still applies. An invalid selector, however, invalidates the whole rule — which is why one typo can appear to break a large block.
.card { colour: red; /* dropped: not a real property */ color: red; /* still applies */ padding: 10; /* dropped: length needs a unit */}The three pillars
Section titled “The three pillars”Everything about “which style actually wins” reduces to three ideas. Each gets full treatment in selectors and the cascade; here is the shape of it.
1. The cascade
Section titled “1. The cascade”Multiple declarations can target the same property on the same element. The cascade is the algorithm that picks one. Simplified, in order:
- Origin and importance — browser default styles lose to your styles;
!importantflips the order. - Specificity — the more specific selector wins.
- Source order — if everything else ties, the declaration that appears later wins.
p { color: blue; }p { color: green; } /* wins: same specificity, appears later */2. Specificity
Section titled “2. Specificity”A selector’s specificity is a triple (id, class, type) compared left to right.
| Selector | Specificity | Notes |
|---|---|---|
* |
0,0,0 | universal selector counts for nothing |
p |
0,0,1 | element (type) selector, also ::before |
.card |
0,1,0 | class, attribute [href], pseudo-class :hover |
#main |
1,0,0 | id |
#main .card p |
1,1,1 | sum of the parts |
1,0,0 beats 0,99,0. Specificity is not a number in base 10 — a hundred classes never outrank one id.
3. Inheritance
Section titled “3. Inheritance”Some properties pass from parent to child automatically; most do not.
body { color: #333; /* inherited: every descendant gets it */ font-family: system-ui;/* inherited */ border: 1px solid red; /* NOT inherited: only body gets a border */}Inherited properties are mostly text-related: color, font-*, line-height, letter-spacing, text-align, visibility, cursor, list-style. Box properties (margin, padding, border, background, width, display) are not inherited.
This is why setting font-family once on body styles the whole page, and why you still have to set border-collapse on the table itself.
A box model preview
Section titled “A box model preview”Every element generates a rectangular box made of four nested layers:
┌─────────────────── margin ───────────────────┐│ ┌───────────────── border ───────────────┐ ││ │ ┌───────────── padding ───────────┐ │ ││ │ │ │ │ ││ │ │ content │ │ ││ │ │ │ │ ││ │ └──────────────────────────────────┘ │ ││ └────────────────────────────────────────┘ │└──────────────────────────────────────────────┘By default, width sets the content width only — padding and border are added on top, so a width: 200px box with padding: 20px and border: 1px occupies 242px. That is almost never what you want:
*, *::before, *::after { box-sizing: border-box;}With border-box, width means the total width including padding and border. Full detail in the box model and layout.
How Tailwind relates to plain CSS
Section titled “How Tailwind relates to plain CSS”Tailwind CSS is not a different language and not a component library. It is a generator of single-purpose CSS classes (utilities), each mapping to one or a few CSS declarations.
.flex { display: flex; }.items-center{ align-items: center; }.gap-4 { gap: calc(var(--spacing) * 4); } /* 1rem */.text-sm { font-size: var(--text-sm); line-height: var(--tw-leading, var(--text-sm--line-height)); }So this markup:
<div class="flex items-center gap-4 text-sm">…</div>is exactly equivalent to writing that CSS by hand and giving the element one class. What you gain is that you never invent a class name, never grow a stylesheet that only ever gets bigger, and never wonder whether deleting a rule breaks something else. What you trade is verbose markup.
Two consequences matter:
- You still need to know CSS.
justify-contentandalign-itemsbehave identically whether you typed them or typedjustify-between items-center. Every page in this section before the Tailwind ones is a prerequisite, not an alternative. - Tailwind v4 is configured in CSS. Since v4 (January 2025) the design tokens live in a
@themeblock in your stylesheet rather than intailwind.config.js:
@import "tailwindcss";
@theme { --color-brand: oklch(0.62 0.19 259); --font-display: "Inter", sans-serif;}That single --color-brand token generates bg-brand, text-brand, border-brand, ring-brand, and so on. The v3 equivalent was a JavaScript object under theme.extend.colors plus a content array; v4 removes both. Both are covered, with the differences called out, in Tailwind fundamentals.
Section map
Section titled “Section map”| Page | What it covers |
|---|---|
| Selectors & the cascade | Every selector type, combinators, pseudo-classes and pseudo-elements, specificity maths, inheritance keywords, custom properties |
| Box model & layout | Content/padding/border/margin, border-box, display, margin collapsing, position, stacking contexts, overflow, normal flow |
| Flexbox | One-dimensional layout: axes, container and item properties, flex shorthand, centring, gotchas |
| Grid | Two-dimensional layout: tracks, fr, repeat(), minmax(), template areas, implicit grid, responsive grids without media queries |
| Responsive & modern CSS | Units, media queries, mobile-first, container queries, clamp(), responsive images, logical properties |
| Visual styling & animation | Colour spaces, gradients, shadows, typography, @font-face, transforms, transitions, keyframes |
| Tailwind fundamentals | Utility-first reasoning, v4 setup, the scales, responsive/state/dark variants |
| Tailwind in practice | Real layouts, @theme customisation, arbitrary values, @apply, component extraction, gotchas |
Key points
Section titled “Key points”- CSS resolves, for every element and every property, exactly one winning declaration — via origin, specificity, then source order.
- Prefer external stylesheets; reserve inline styles for runtime-computed values.
- Set
box-sizing: border-boxglobally before you do anything else. - Inheritance covers text properties only; box properties never inherit.
- Tailwind emits ordinary CSS. Learning CSS is learning Tailwind; the reverse is not true.