A language tour
Misunderstood as decoration. Actually a constraint-solving layout engine, a cascade algebra, and an animation system — all in one.
01 — The Cascade
CSS stands for Cascading Style Sheets. The cascade is the algorithm that decides which rule wins when multiple rules target the same element — weighing origin, specificity, and order. Understanding it ends the "why won't this work" frustration permanently.
"The cascade is not a bug, not a quirk, and not something to fight. It's the most powerful feature in the language — once you understand it."
— Every senior CSS developer, eventually/* Specificity: inline > ID > class > element */ p { color: black; } /* 0-0-1 */ .note { color: grey; } /* 0-1-0 */ #intro { color: navy; } /* 1-0-0 ← wins */ /* Inheritance — children inherit text properties from parents */ body { font-family: Georgia, serif; line-height: 1.6; /* every element inherits this — you set it once */ } /* The :is() selector reduces specificity noise */ :is(h1, h2, h3) { font-weight: 700; line-height: 1.2; } /* Layers — explicit control over the cascade order */ @layer reset, base, components, utilities; @layer utilities { .visually-hidden { position: absolute; width: 1px; clip: rect(0 0 0 0); } }
@layer (CSS 2022) lets you declare the cascade order upfront — utilities always beat components, components always beat base — no more specificity wars.
02 — Custom Properties
CSS custom properties aren't just variables — they're scoped, inheritable, and live values that update in real time. A component can override a property defined on the root, and every child of that component sees the override. This is how design tokens work.
/* Define at root — available everywhere */ :root { --color-accent: #264DE4; --color-surface: #F5F7FF; --spacing-base: 1rem; --radius: 8px; } /* Dark mode — override the same variables, nothing else changes */ @media (prefers-color-scheme: dark) { :root { --color-accent: #6B8FFF; --color-surface: #090C1E; } } /* Component-level override — scoped to .card and its children */ .card { --radius: 16px; background: var(--color-surface); border-radius: var(--radius); padding: calc(var(--spacing-base) * 1.5); } /* With a fallback — graceful when undefined */ .badge { color: var(--badge-color, var(--color-accent)); }
Dark mode with zero JavaScript — redefine the same custom properties inside a @media (prefers-color-scheme: dark) block and the entire theme inverts.
03 — Grid Layout
CSS Grid is the layout system the web always needed. Before it, developers built grids with floats, tables, flexbox workarounds — each a clever hack. Grid was designed specifically for two-dimensional layout and it shows: the code describes what you want, not how to fake it.
/* Named areas — the layout written as a picture */ .page { display: grid; grid-template-columns: 240px 1fr; grid-template-rows: auto 1fr auto; grid-template-areas: "header header" "sidebar main " "footer footer"; min-height: 100vh; } .header { grid-area: header; } .sidebar { grid-area: sidebar; } .main { grid-area: main; } .footer { grid-area: footer; } /* Auto-fit responsive grid — no media queries needed */ .card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 1.5rem; /* cards reflow from 1 → 2 → 3 columns automatically */ } /* Subgrid — children share the parent's grid lines */ .card { display: grid; grid-row: span 3; grid-template-rows: subgrid; /* align across cards */ }
repeat(auto-fit, minmax(260px, 1fr)) — three columns on desktop, two on tablet, one on mobile, with zero media queries. Grid figures it out.
04 — Selectors
CSS selectors are a mini query language for the document. Modern selectors can express things like "the last three items in a list of more than five" or "any paragraph that doesn't contain a link" — without JavaScript touching the DOM.
/* :has() — the parent selector CSS never had, until now */ .card:has(img) { padding-top: 0; /* card with an image gets no top padding */ } form:has(:invalid) { border-color: red; /* form turns red if any field is invalid */ } /* :nth-child with an expression */ li:nth-child(3n + 1) { color: blue; } /* 1st, 4th, 7th… */ /* :not() with a complex selector */ p:not(.lead):not(:last-child) { margin-bottom: 1rem; } /* Attribute selectors — pattern match on HTML attributes */ a[href^="https"] { color: green; } /* starts with https */ a[href$=".pdf"] { color: red; } /* ends with .pdf */ a[href*="github"] { color: purple; } /* contains github */ /* :where() — zero specificity, overridable anywhere */ :where(article, section) p { max-width: 65ch; }
form:has(:invalid) styles the parent based on a child's state — something JavaScript developers wrote utility functions for. Now it's a single CSS selector.
05 — Animations & Transitions
CSS can animate almost any visual property — position, size, colour, opacity, shape — without a single line of JavaScript. Transitions handle the simple case. Keyframes handle the complex. And animation-timeline ties motion to the scroll position itself.
/* Transition — smooth change between states */ .button { background: var(--color-accent); transition: background 0.2s ease, transform 0.15s ease; } .button:hover { background: oklch(from var(--color-accent) calc(l + 0.1) c h); transform: translateY(-2px); } /* Keyframes — full choreography */ @keyframes fadeSlideIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .hero-title { animation: fadeSlideIn 0.7s cubic-bezier(0.22, 1, 0.36, 1) forwards; } /* Scroll-driven animation — no JavaScript whatsoever */ @keyframes reveal { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } } .card { animation: reveal linear both; animation-timeline: view(); /* tied to scroll position */ animation-range: entry 0% entry 40%; }
animation-timeline: view() links an animation directly to the element's position in the viewport — scroll-driven reveals with zero JavaScript.
06 — The Whole Picture
Style a component based on its own container's size — not the viewport. Components finally become truly self-contained.
A perceptually uniform colour space. Equal steps in lightness actually look equal. Generate accessible colour palettes with a formula, not a guess.
margin-inline-start instead of margin-left — layout that adapts to writing direction automatically. RTL support for free.
One font file with axes for weight, width, and optical size. Animate typography. Stop downloading 8 font files for one typeface.
Native CSS nesting arrived in 2023. Write .parent { & .child { } } without a preprocessor — Sass-style structure in plain CSS.
Animate between pages or states with CSS keyframes. Full-page transitions that used to require a JavaScript framework, now native.