CSS:DeclarativeStyle
CSS is not a general-purpose programming language — it is the declarative styling language Håkon Wium Lie proposed from CERN in 1994. In 30 years it climbed out of the IE6 dark age and, through Flexbox / Grid / Custom Properties / :has() / container queries, became the layered foundation of the Web's look. In 2026 it has natively reclaimed jobs SPA frameworks used to own — "the platform caught up." It is also, incidentally, Turing-complete.
Håkon Wium Lie
first standard
"layout finally worked"
:has · @container · OKLCH
What is CSS
CSS = Cascading Style Sheets. It is declarative: you describe what you want to see, the browser decides how to arrange it. It is not a general-purpose programming language in the Turing-machine sense — yet, surprisingly (see §07), it is Turing-complete.
No loops, no assignments. You say display: flex and the browser arranges things — you describe the goal, not the steps. Same family as SQL / HTML / Make; nothing like JS / Python.
When multiple rules hit the same property, an (inline, ID, class, type) tuple decides which wins. This is what the "C" stands for — and the most common stumbling block for newcomers.
CSS only speaks to elements that already exist. HTML provides structure, CSS provides look, JS provides behaviour — the content / style / behaviour separation is the Web's 30-year foundation.
Since 2005 the W3C abandoned a single CSS 3 spec, splitting it into Selectors L4, Color L5, Containment L3 and friends. "CSS 3" is a label, not a version — which is why the language keeps moving 20 years on.
// imperative: you write each step
const row = document.querySelector('.row');
const children = row.children;
const width = row.offsetWidth;
const per = width / children.length;
for (let i = 0; i < children.length; i++) {
children[i].style.left = (i * per) + 'px';
children[i].style.width = per + 'px';
}
// resize? do it all again/* declarative: say what you want */
.row {
display: flex;
gap: 12px;
}
.row > * { flex: 1; }
/* responsive · accessible · GPU-composited
the browser handles it all */History : Timeline
From Håkon Wium Lie's 1994 CERN email, through the IE 6 dark decade, to the spring of 2017 when four browsers shipped Grid in the same season, to the 2023 trio of container queries / :has() / nesting — CSS's 30 years are a slow but unbroken curve.
- 1994·10·10
Håkon Wium Lie's proposal at CERN
October 10. From CERN, Norwegian engineer Håkon Wium Lie sends out an email titled "Cascading HTML Style Sheets — a proposal". The Web at the time had no notion of separated styling — HTML controlled its own look, browsers each painted differently. Lie's "cascade" idea was simple: author, user, browser each declare styles, merged by priority. The semantic model of CSS as we know it starts here.
- 1996·12·17
CSS Level 1 — W3C Recommendation
Late 1996: W3C ratifies CSS 1 as an official Recommendation. The scope is conservative: fonts, colours, alignment, margin/padding, simple selectors. But the structural break is done — style is separated from markup. Netscape briefly pushes JSSS (JavaScript Style Sheets) as a rival; it does not survive.
- 1998·05
CSS 2 — positioning and media
CSS 2 brings positioning (
position: absolute / relative / fixed), media types (@media print), z-index, generated content (::before / ::after). Beautiful on paper — but browser implementations were too broken to rely on. This kicks off the dark decade of "table-based layout": nested<table>+ 1×1 transparent-GIF spacers. - 2001·08
IE 6 ships — the CSS dark age begins
IE6 ships the broken box model as a mainstream default, plus its own proprietary hacks:
* htmlselectors only IE6 sees,_propertyunderscore-prefix only IE6 parses. Front-end engineers learn to carry conditional comments<!--[if lte IE 6]>and a thick pile of hack cheatsheets. This era runs until roughly 2010 — Microsoft took nearly a decade to seriously update IE. - 2003·05
CSS Zen Garden launches
Dave Shea's CSS Zen Garden: identical HTML, different CSS, radically different looks. The site argued more convincingly than any spec that "style can be fully independent of structure." A generation of front-end developers learns CSS by studying its submissions.
- 2005—2010
CSS 3 modularised — no more "single CSS3 spec"
W3C abandons the "single CSS 3 document" plan and breaks the language into independent modules: Selectors L3, Color L3, Media Queries L3, Backgrounds & Borders L3, and on. Each module ships its own Candidate / Recommendation. "CSS 3" becomes a marketing label, not a spec. This is what lets CSS evolve continuously for the next 15 years without log-jamming.
- 2009·06
Media Queries Level 3
The technical seed of responsive design:
@media (max-width: 768px)becomes a Recommendation. Ethan Marcotte's 2010 article "Responsive Web Design" takes it mainstream. "Mobile sites" stop meaning a separate m.example.com domain and start meaning "one HTML + CSS that adapts." - 2010—2018
The preprocessor era — Sass / Less / Stylus
CSS itself had no variables, no nesting, no mixins since 1996 — so the community built its own. Sass (2007), Less (2009), Stylus (2010) compile down to CSS. Around 2015,
node-sass+gulp+gruntwas the standard front-end pipeline. This era shaped what CSS itself later prioritised —--var,@nest, the lot. - 2012·09
Flexbox stabilises
Flexbox reaches Candidate Recommendation; browser support lands progressively from 2013 (Chrome) → 2015 (Safari + IE 11). "Layout with flex" is now possible — goodbye
float+clearfix, goodbyevertical-align: middleastrology. The first time layout has the right semantics. - 2016·09
CSS Custom Properties — native variables
Chrome 49 (early 2016) → Firefox 31 → Safari 9.1: native CSS variables with
--var+var(). The key thing: these are real runtime values, mutable from JS, swappable inside@media— Sass's compile-time variables cannot do this. From here on, "do we still need Sass?" begins to be a fair question. - 2017·03
CSS Grid lands in every browser the same year — "layout finally worked"
March 2017: Firefox 52 → Chrome 57 → Safari 10.1 → Edge 16, all four major browsers shipping Grid Layout in the same spring. The most synchronised release in CSS history. Two-dimensional layout finally has native semantics — every "multi-column" layout before this was a hack. Jen Simmons's "Everything You Know About Web Design Just Changed" talk marks the moment.
- 2017·11
Tailwind CSS — utility-first
Adam Wathan ships Tailwind: "use class names as styles". Instantly polarising — OOCSS / BEM purists call it a regression to the 90s; the utility-first camp calls it the endgame: "design system in your CSS file." Whichever side, Tailwind drags the utility-first mindset back into the mainstream and directly shapes the later CSS Modules / CSS-in-JS debate.
- 2018—2023
The rise and fall of CSS-in-JS — the platform caught up
styled-components (2016) and Emotion (2017) put CSS inside JS, and trend hard for a few years. Post-2020 the trade-offs surface: runtime cost, SSR pain, weak tree-shaking. Around 2024, Tailwind + CSS Modules + plain CSS reclaim the mainstream. "The platform caught up" is the defining 2023-2025 narrative:
:has(), container queries, nesting,color-mix, scroll-driven animations — everything that used to need JS now runs in CSS. - 2023·02—08
Container queries /
:has()/ nesting — three landmarks in one yearContainer queries (Chrome 105 2022-08, Firefox 110 2023-02, Safari 16 2022-09): elements size themselves to a parent container, not the global viewport — a feature requested for a decade.
:has()(Safari 15.4 2022-03, Chrome 105, Firefox 121 2023-12): the "parent selector", long deemed unimplementable, finally ships. CSS Nesting: Sass-style nesting becomes native. Any of these alone would be a landmark; 2023 ships them together. - 2024·03
Scroll-driven animations — no JS required
Chrome 115 (2023-07) → Firefox 127 (2024-06):
animation-timeline: scroll()andview-timeline. Scroll- and viewport-driven animations now run entirely in CSS, no IntersectionObserver + RAF needed. The work continues — Safari is still catching up — but the model is clear: "animation = timeline + keyframes + trigger", and scroll is just one possible trigger. - 2024·09
View Transitions API — cross-page animation, natively
Chrome 111 (2023) introduced same-document transitions; 2024 adds cross-document view transitions: a full-page navigation can now interpolate elements SwiftUI-style with not a line of JS. Safari and Firefox are following. A clear signal of "CSS reclaiming SPA-framework territory" — layout animations that used to require React / Vue are now a browser primitive.
- 2024·12
Anchor Positioning — elements positioned relative to each other
Chrome 125 ships
anchor-name+position-anchor: tooltips, popovers and context menus no longer need JS to compute coordinates. One element declares itself an anchor; another positions itself relative to it, with the browser handling scroll, zoom and viewport-edge clipping. The kind of feature you wonder how the Web went 30 years without. - 2025·06
@scopereaches mainstream — local styles without toolingChrome 118 (2023) shipped
@scopeearly; by 2025 Firefox and Safari are in line. Style scoping no longer depends on CSS-Modules hash compilation or BEM naming discipline — write@scope (.card) {…}directly. Another textbook "was tooling, now native" case. - 2026·now
CSS at 30 — the platform caught up
CSS in 2026: variables, nesting, container queries, :has, scope, view transitions, scroll-driven animations, anchor positioning, OKLCH colour — all native. Sass and styled-components are optional, no longer required. Tailwind stays popular for DX, but the underlying platform is finally strong enough on its own. This very page is written in CSS — its live grid / container-query /
:has()demos are themselves the answer.
Language Essentials : CssAlphabet
The eight cards below are CSS's core machinery: cascade/specificity, box model, flexbox, grid, custom properties, :has(), container queries, OKLCH colour. The ninth riffs on "is CSS even a language?"
Cascade + specificity
The "C" in CSS: cascading. When multiple selectors hit the same property, an (inline, ID, class/pseudo/attr, type/pseudo-elem) tuple decides — higher wins. "!important" is the nuclear option, use sparingly.
.btn /* (0,0,1,0) */
#hero .btn /* (0,1,1,0) wins */
a:hover /* (0,0,1,1) */
[disabled] /* (0,0,1,0) */Box model — content / padding / border / margin
Every element is a four-layer box. CSS 2 defaulted to content-box (width = content only); modern stylesheets nearly all flip to box-sizing: border-box so width includes padding+border — the "wrong" IE5 behaviour everyone after 2010 admits was actually more sensible.
*, *::before, *::after {
box-sizing: border-box;
}
/* width 360px = total · not content */Flexbox — one-dimensional layout
Spec stabilised in 2012, browsers caught up 2013-2015. One axis + main/cross: it killed three of the oldest CSS pain points — inline alignment, splitting leftover space, vertical centring.
.row {
display: flex;
gap: 12px;
align-items: center;
justify-content: space-between;
}Grid — two-dimensional layout
2017 saw all four major browsers ship Grid in the same year — the most synchronised release in CSS history. Rows + columns at once, named areas, auto-placement. Every "multi-column" before this was a hack; Grid is when the word "layout" finally meant what it says.
.page {
display: grid;
grid-template-columns: 240px 1fr;
grid-template-rows: auto 1fr auto;
}Custom properties (--var)
Universally supported since 2016. True runtime values: mutable from JS, overridable inside @media, inherited through subtrees — Sass compile-time variables cannot do this. They are also the typed foundation of Houdini.
:root { --accent: #1572B6; }
.btn {
background: var(--accent);
}
// runtime: el.style.setProperty('--accent', '#fff')The parent selector, :has()
For 20+ years, "select the parent" was considered impossible (performance horror). 2022-2023 Safari / Chrome / Firefox all shipped it — browsers found a lazy-evaluation implementation. The single biggest selector-level advance in a decade.
/* row with a checked child */
li:has(input:checked) {
background: color-mix(in oklch, accent 15%, transparent);
}Container queries (@container)
The next generation of responsive: elements react to their parent container's width, not the viewport. The same Card component is narrow in a sidebar, wide in the main column, and rearranges itself accordingly — the first component-level responsive primitive.
.card-wrap { container-type: inline-size; }
@container (max-width: 320px) {
.card { flex-direction: column; }
}oklch() / color-mix()
Perceptually uniform colour arrives in CSS. RGB and HSL distort luminance; OKLCH (L = luminance, C = chroma, H = hue) finally gives design systems a reliable "lighten by 10%." Adopted by major design-token systems through 2024-2025.
:root {
--blue-500: oklch(50% 0.20 250);
--blue-600: color-mix(in oklch, var(--blue-500), black 15%);
}"Is CSS a programming language?"
The Stack Overflow / Reddit perennial. Strictly — it's a declarative DSL, not a general-purpose programming language (no I/O, no mutable state, no built-in abstraction over a Turing machine). But (see §07) HTML + CSS can encode Rule 110, so it is Turing-complete in the theoretical sense. Verdict: yes a language, no not a general-purpose one.
"CSS isn't a bad programming language; it's a great styling language. Critiquing it as if it were a programming language is a category error."
Live Demos : ItsActuallyCss
This section is actually running CSS — not screenshots, not code blocks. Open DevTools and tweak — it all updates live. The existence of this section is itself the argument: CSS can demonstrate itself.
.row { display: flex; gap: 10px; } .row > * { flex: 1; } .row > *:nth-child(2) { flex: 2; }
.page { display: grid; grid-template-columns: 1fr 2fr 1fr; grid-template-rows: 60px 60px; } .page > :first-child { grid-column: span 3; }
span 3 covers the row. All four browsers shipped this in the same 2017 spring..wrap { container-type: inline-size; } @container (max-width: 320px) { .card { flex-direction: column; } }
label:has(input:checked) { background: linear-gradient(135deg, var(--css), var(--css-deep)); color: white; }
:has(): the "parent selector" — long thought impossible, shipped 2022-2023. No JavaScript in this demo. Tick a checkbox and watch the whole <label> recolour.@keyframes float { 50% { transform: translateY(-18px) scale(1.08); } } .box { animation: float 3s ease-in-out infinite; }
/* perceptually uniform · OKLCH */ .s50 { background: oklch(50% 0.20 250); } .s60 { background: oklch(60% 0.20 250); } /* +10% L = perceptual +10% lightness */
Specificity calculator
CSS selector precedence = the tuple (inline, ID, class/pseudo/attr, type/pseudo-elem), compared lexicographically left-to-right.
*pp::first-line.btna:hover[type="text"]#hero .btn#hero #cta.btn:hoverstyle="color:red"!important overrides all2010 vs 2026 : BeforeAfter
CSS's most interesting 30-year story is "used to be a hack, now is one line." Four side-by-sides below: same goal, top is actual code from 2007-2014, bottom is the native answer from 2017-2024.
Clearing floats — clearfix → flex
.row { overflow: hidden; }
.col { float: left; }
.clearfix::after {
content: "";
display: table;
clear: both;
}
/* 真实工程里还套 zoom:1 给 IE6 */.row {
display: flex;
gap: 12px;
}
/* done. */Why CSS deserves real depth : WhyCss
"It's just changing colours" is the most common newcomer misread. People who use CSS deeply know it's declarative, a performance lever, the accessibility entry point, a 30-year-backward-compatible living layer, and the layer of the Web with the most momentum in 2026.
"Declarative" is the feature, not the flaw
CSS doesn't tell the browser how to lay things out — only what the result should be, and lets the layout engine decide. It looks inflexible; in reality it's the foundation of 30 years of backward compatibility, platform portability and accessibility. Anyone who has hand-drawn layout in Canvas understands.
/* 浏览器决定怎么算; you say what */
.row {
display: flex;
gap: 12px;
}Performance is the browser's job, not yours
The three-stage pipeline — layout, paint, composite — is all GPU-accelerated by the browser. A single transform: translateZ(0) hoists an element onto its own GPU layer. Compared to hand-rolled React/Canvas diffs, CSS is a performance lever — provided you pick the right property (mutate transform, not top).
/* GPU-composited */
.fly { transform: translateY(-10px); }
/* relayout · slow */
.bad { top: -10px; }Accessibility + OS preferences out of the box
prefers-reduced-motion, prefers-color-scheme, prefers-contrast — system-level user preferences are queryable directly from CSS. No navigator probing, no JS listeners. Users really do toggle "reduce motion", and a single CSS rule respects it.
@media (prefers-reduced-motion: reduce) {
* { animation-duration: 0.01ms!important; }
}The platform caught up — fewer wheels to reinvent
The 2015-2022 stack — Sass for variables, styled-components, Modernizr, Animate.css, polyfill soups — is largely unneeded in 2026. Native --var, @scope, :has, container queries, scroll animations have all landed. "The platform caught up" is the cleanest summary of the last three years in front-end.
/* 2018 — needed Sass */
/* 2026 — native */
.card {
--pad: 16px;
padding: var(--pad);
& h2 { color: white; }
}30 years of backward compatibility
CSS 1 code written in 1996 still runs today — no browser drops old specs. This is the Web platform's superpower: old code keeps working without maintenance, new features stack on through progressive enhancement. More stable than any "modern" language.
/* CSS 1 (1996) · still valid */
body {
font-family: serif;
color: #333;
}Aside : CssIsTuringComplete
Yes — CSS is Turing-complete — not in the casual hand-wave sense, but in the "paper exists, runnable demo exists" sense.
HTML + CSS can simulate Rule 110
In 2011, Eli Fox-Epstein demonstrated an HTML + CSS construction simulating the Rule 110 cellular automaton. Since Rule 110 was proven Turing-complete by Matthew Cook in 2004, HTML + CSS is Turing-complete by reduction.
The trick: :checked stores state, :hover + label[for] provides the trigger, the sibling combinator ~ handles neighbour propagation. Each manual hover/click simulates one clock tick — slow, yes, but Turing-completeness does not require speed.
Of limited practical value — no one writes Fibonacci in CSS. But it shows CSS's expressive power is quietly substantial, and it's the supporters' trump card in the eternal "is CSS a programming language?" debate. Verdict: a complete language, but the wrong tool for general computation.
Ecosystem / Docs / Tools : Ecosystem
CSS has no single owner — the W3C CSSWG drives the standard, MDN + web.dev teach it, the tool chain runs Sass → PostCSS → Tailwind. Below: the key pillars of CSS's 30-year ecosystem.
vs HTML / JS : CssVsTheRest
The Web's three layers: HTML for structure, CSS for appearance, JavaScript (TS) for behaviour. Each is its own language; together they form progressive enhancement — usable without JS, readable without CSS.
| HTML | CSS | JavaScript | |
|---|---|---|---|
| Origin | Tim Berners-Lee · 1991 | Håkon Wium Lie · 1994 | Brendan Eich · 1995 |
| Paradigm | Structural markup | Declarative styling | Imperative / multi-paradigm |
| Turing-complete | No (pure markup) | Yes (combined with HTML · Rule 110) | Yes (general-purpose) |
| Evolution pace | Slow · WHATWG living standard | Medium · modular (Color L5, Selectors L4...) | Fast · TC39 yearly |
| Mutable state | None | None (user-driven: :hover / :checked) | Fully mutable (heap / closures) |
| Side effects / IO | None (browser parses) | None (browser renders) | Yes (fetch / DOM / Worker...) |
| Backward compat | 30+ years, never breaks | 30 years, never breaks | Strict; occasional TC39 deprecations |
| GPU-accelerated | — | Native (transform / filter) | Manual (canvas / WebGL / WebGPU) |
| Accessibility hooks | Semantic elements + ARIA | prefers-* media queries | Manual (focus / live regions) |
| File size | Small · direct gzip | Medium · < 100KB gzipped typical | Large · frameworks reach MB |
Outlook : TheRoadAhead
CSS in 2026: the platform has caught up, but it's still moving. Scroll-driven animations, view transitions, anchor positioning, Houdini and the Typed OM are four directions on the visible road — each reclaiming work that JS frameworks used to own.
Scroll-driven animations + view transitions — SPA-framework jobs reclaimed by the platform
For a decade, entry animations, scroll-driven parallax, and cross-page morphs all required React / Vue / Framer Motion + IntersectionObserver + RAF. 2024-2026: animation-timeline + view-transition native — one CSS rule does the same job 10× faster, with no flicker, no jank. Safari is still catching up but the direction is settled.
The bigger story: CSS is genuinely reclaiming territory from SPA frameworks for the first time. :has() took back DOM querying; now animation. "The platform caught up" isn't a slogan, it's an observation.
Anchor positioning — tooltips and popovers without JS
A 30-year pain point: making a tooltip follow a button, or having a popover dodge viewport edges, used to require getBoundingClientRect() + resize listeners. Chrome 125 (Dec 2024) ships anchor-name, Firefox and Safari following. A long-missing piece finally arriving.
Houdini Paint API — CSS plugins
The CSS engine used to be a black box to developers. The Houdini Paint and Layout APIs expose paint(myThing) to JS — JS becomes a brush for CSS. Canvas-style patterns, custom masks, procedural textures all become CSS. The spec is full; browser implementations vary.
Typed OM + typed custom properties
CSS has always been a string soup: JS reads el.style.width and gets "320px", splits it by hand. The Typed OM makes it CSSUnitValue(320, "px") — addable, subtractable, comparable. Combined with @property giving custom properties types, defaults, and inheritance flags, CSS gains its first real "type system".