1991 → 2026

A History of CSS

30 years of styling the web. The proposals nobody accepted, the hacks everyone used, and the features we waited a decade for.

7
Eras
30+
Years
40+
Milestones
4
Dark age hacks
Before CSS (BC) 1991 – 1995
1991 HTML is born. Styling is the browser's problem.

Tim Berners-Lee releases HTML at CERN. There is no way to style a page. The browser decides what everything looks like. Font size, colors, spacing — all hardcoded per implementation. This is fine for sharing documents. Not for building anything that needs to look a particular way.

1993 Mosaic ships. Inline images arrive.

The NCSA Mosaic browser introduces the <img> tag (over objections from Berners-Lee, who wanted a different approach). Background colors become possible. Styling is still entirely per-browser. There is no standard.

1994 The <FONT> tag. The web's first styling tool.

Netscape introduces <FONT face="..." size="..." color="...">. It works. Everyone uses it. It is deeply presentational HTML — structure and style tangled together. The same tag repeated on every paragraph. Every heading. Every cell in every table. A site with 50 pages means 50 files to update when you change the font.

1994 Tables as layout begins.

Netscape ships <CENTER> and <BLINK> as HTML tags. More importantly, browsers start honoring <TABLE> for visual layout — not just tabular data. Transparent 1x1 GIFs stuffed into table cells become a technique for pixel-perfect spacing. This approach will dominate for the next decade.

The Proposal 1994 – 1996
Oct 10, 1994 Håkon Wium Lie proposes CSS.
Origin

Håkon Wium Lie publishes a proposal called "Cascading HTML Style Sheets" at the Mosaic and the Web conference in Chicago. The key idea is the cascade: multiple style sheets each contributing rules, with weights to resolve conflicts. Lie is working at CERN with Berners-Lee at the time. This is the document CSS descends from.

1994–1995 Bert Bos joins. The name becomes CSS.

Bert Bos, who had been developing a separate style sheet proposal for the Argo browser, contacts Lie. They merge their ideas. The cascading aspect from Lie's proposal survives. The name "Cascading Style Sheets" is settled on. They bring the work to the W3C.

1995 Netscape proposes JSSS. It dies.

Netscape counters with JavaScript Style Sheets — styles written entirely in JavaScript, applied via the DOM. document.tags.P.marginLeft = 12;. The W3C is not enthusiastic. Netscape ships it in Navigator 4, then quietly drops it. Total market adoption: near zero.

Dec 17, 1996 CSS Level 1 is a W3C Recommendation.
Landmark

CSS1 is published as an official standard. 68 properties. Colors, fonts, margins, padding, borders, text alignment, basic display values. No layout properties — floats and positioning come later. The spec exists. Now someone has to implement it correctly.

CSS1 (Anno Domini) 1996 – 1998
1996 IE3 ships the first browser CSS support.

Internet Explorer 3 is the first widely used browser to support CSS. The implementation is partial and buggy. The box model behaves differently than the spec. Some properties are ignored. Some values do surprising things. But it ships, and web developers start experimenting.

1997 Netscape 4 ships CSS support. Also buggy.

Navigator 4 adds CSS support that manages to be worse than IE3's. position: relative breaks layout. Dynamic styles via JavaScript fail silently. Tables with font-size produce wrong results. The short version: web developers learn to write CSS defensively, testing in both browsers and assuming nothing works the same way twice.

1998 IE4 improves CSS compliance. Slightly.

Internet Explorer 4 ships with meaningfully better CSS support. Still not the spec, but closer. The box model bug remains. position: absolute works more predictably. The DHTML era begins — JavaScript manipulating CSS properties to animate elements. CSS is starting to feel like a real tool.

CSS2 & the Browser Wars 1998 – 2003
May 1998 CSS2 becomes a W3C Recommendation.
Landmark

CSS Level 2 is published. Major additions: the position property, z-index, @media types (including print), the content property for generated content, cursor, and the :before and :after pseudo-elements. CSS now has a proper layout model on paper. In browsers, support is still fragmented.

1998–2001 The browser wars. Write everything twice.

IE and Netscape implement CSS differently enough that the same stylesheet produces different results in each browser. Web developers respond by writing browser-specific rules, using JavaScript to detect the browser, or simply giving up on CSS and staying with tables. "Best viewed in Internet Explorer 4.0" banners proliferate.

2000 IE5 for Mac ships the most accurate CSS renderer of its era.

Tasman, the rendering engine in Internet Explorer 5 for Mac, passes the CSS1 test suite better than any browser of its time. It gets the box model right. Developers who test on it are briefly optimistic. Then IE6 for Windows ships, and optimism evaporates.

2001 IE6 ships. Dominates for nine years.
Inflection point

Internet Explorer 6 achieves near-total market dominance. It implements the box model wrong: width includes padding and border instead of only the content area. It has dozens of layout bugs. It does not update for 5 years. For the next decade, every CSS technique is shaped by what IE6 can and cannot do.

The Dark Ages 2001 – 2010
~2002 The box model hack.
CSS Horror

IE6's wrong box model means width calculations are off everywhere. The fix: use a CSS parsing bug. Writing voice-family: "\"}\""; voice-family: inherit; confuses IE into stopping early, so you can feed the wrong-box-model IE a faked-up width and all other browsers the real one. This is what production CSS looks like in 2002.

~2003 The star hack.
CSS Horror

* html .foo {}. IE6 incorrectly treats * as matching the root element, so this rule only applies in IE6. Every front-end developer knows this selector by heart. It becomes the standard way to write IE-only overrides. CSS files develop a two-column structure: the real rule, then the starred override underneath.

~2003 zoom: 1. Not a real property. Works anyway.
CSS Horror

IE has a concept called "hasLayout" — an internal flag that controls whether an element manages its own size and position. Many IE bugs only affect elements without layout. The fix is triggering hasLayout by applying zoom: 1 — a non-standard IE-only property that does nothing visible but flips the internal flag. It appears in every serious IE-compatible stylesheet.

~2004 The clearfix. Floats don't clear themselves.
CSS Horror

The entire layout model of this era is floats. Float a sidebar left, float content right, done. Except floated elements are removed from the normal flow, so their parent collapses to zero height. The fix: a generated ::after pseudo-element with content: ''; display: table; clear: both; after every layout container. Copy-pasted into every stylesheet for 15 years.

2004 Firefox 1.0 ships. Real CSS2 support.

Firefox 1.0 launches as the first serious challenge to IE's dominance in years. Its CSS support is accurate. Selectors work. The box model is correct. Developers who use it feel what CSS was supposed to be. IE still has 90%+ market share, but things are starting to shift.

2005–2006 CSS2.1 drafts. Vendor prefixes begin.

The W3C starts CSS2.1, a corrected version of CSS2 that reflects what browsers actually implement rather than what the spec intended. Meanwhile, browsers begin shipping experimental CSS3 features behind vendor prefixes: -webkit-, -moz-, -ms-, -o-. Every new feature needs four lines. Some features end up prefixed for seven years.

2008 Safari ships -webkit-border-radius. Rounded corners.

Before this, rounded corners required 3–4 images, extra markup, and nested divs. Safari 3 ships -webkit-border-radius, and developers use it the same day. Firefox follows with -moz-border-radius. IE users see square corners for another three years. Designers start asking for rounded corners constantly.

CSS3 Renaissance 2009 – 2016
2009 CSS3 goes modular. No more monolithic spec.

The W3C splits CSS into separate modules: Selectors, Colors, Backgrounds, Transforms, Transitions, Animations, Flexbox, Grid. Each can advance at its own pace. This is why there is no single "CSS3 release date" — different modules ship in different browsers across different years. CSS development is now continuous, not version-based.

2009–2010 Transitions and transforms ship. Animations without JavaScript.

CSS Transitions (-webkit-transition) and CSS Transforms (-webkit-transform: rotate(45deg)) arrive first in Safari, then Firefox. For the first time, you can animate a property by changing its value — the browser interpolates the intermediate frames. JavaScript animators start losing their jobs to a single line of CSS.

2011 CSS2.1 becomes a W3C Recommendation. 13 years after CSS2.

CSS2.1, the corrected and normative version of CSS2, finally achieves Recommendation status. It took from 2004 to 2011. The spec now matches what browsers actually implement. This is also the year IE9 ships — the first IE version with serious CSS3 support including SVG, border-radius, and CSS3 selectors.

2012 Flexbox. Layout without floats.

The first Flexbox draft used display: box with completely different property names. The spec was revised twice before stabilizing. By 2012, -webkit-flex ships in Chrome. By 2013, Firefox has it. For the first time, vertical centering is one line of CSS. Clearing floats stops being a topic of discussion — slowly.

2013 @keyframes and CSS Animations ship.

CSS Animations add @keyframes, animation-duration, animation-timing-function, animation-iteration-count. Complex multi-step animations without a single line of JavaScript. jQuery's .animate() function starts looking obsolete. Vendors still require prefixes: @-webkit-keyframes, @-moz-keyframes.

2015 Flexbox unprefixed and stable across all major browsers.

Flexbox reaches baseline support without vendor prefixes. The clearfix hack starts dying out. Vertical centering becomes easy. Float-based grid systems — which had been the foundation of every major CSS framework — are gradually replaced. Bootstrap 4 will eventually drop its float-based grid for Flexbox in 2018.

2016 IE6 end of life. 9 years after IE7 shipped.

Microsoft officially ends support for IE6 on January 12, 2016. It had been unsupported since 2014, but enterprises kept using it. At peak, IE6 had over 90% market share. It held back CSS adoption for a decade. Its death is celebrated by web developers worldwide. Some buy champagne. Some make t-shirts. Some do both.

Modern CSS 2017 – present
Mar 2017 CSS Grid ships in Chrome and Firefox simultaneously.
Landmark
display: grid — 3 columns, 2 rows

Chrome 57 and Firefox 52 ship CSS Grid on the same day — a coordinated release specifically to avoid the vendor prefix era repeating. Two-dimensional layout, native to CSS, for the first time. Rows and columns defined in the stylesheet. Items placed by grid area name. A decade of float-based layout frameworks become unnecessary overnight.

CSS Grid snippet
2017 CSS Custom Properties ship everywhere.
Landmark
Click to change --color

CSS custom properties — --my-color: blue;, color: var(--my-color); — ship in all major browsers. They are real variables that cascade, inherit, and can be updated at runtime. Sass preprocessors lose their main selling point. Theming with JavaScript becomes possible without inline styles: change one custom property on :root and every element that uses it updates.

Custom properties snippet
2021 clamp() widely available. Fluid sizing without media queries.
font-size: clamp(0.85rem, 2.5vw, 1.4rem)
Resize the window to see this scale.
min 0.85rem → fluid 2.5vw → max 1.4rem

clamp(min, preferred, max) picks a value within bounds. font-size: clamp(1rem, 2.5vw, 2rem) produces fluid typography that scales with the viewport but never goes below 1rem or above 2rem. No media queries. No JavaScript. The technique that had required a full fluid typography library fits in one property value.

clamp() snippet
2022 CSS @layer ships. The cascade gets explicit order control.

@layer lets you group rules into named layers and declare their priority order: @layer base, components, utilities;. Utilities always win over components, regardless of specificity. The !important wars — where teams escalate specificity to override each other's styles — have a structural solution. Third-party library styles can be put in a low-priority layer and overridden cleanly.

@layer snippet
2022–2023 CSS Nesting lands. Write CSS inside CSS.

Native CSS nesting ships in Chrome 112, Firefox 117, and Safari 16.5. You can write .card { color: red; &:hover { color: blue; } } without a preprocessor. Sass users shrug. PostCSS-nesting maintainers update their READMEs. The main argument for using Sass in 2024 is mostly habit.

CSS Nesting snippet
2023 Container Queries. The most requested feature for 10 years.
Landmark

Container Queries ship in all major browsers. Style an element based on the size of its container, not the viewport. A card component that reflows its layout when placed in a narrow sidebar without needing to know anything about the page it's in. Developers had been asking for this since media queries were introduced. It took a decade.

Container Queries snippet
2023 :has() ships. The parent selector, finally.
:has(input:checked) changes the parent
  • Pure CSS. No JavaScript.
  • Parent styles child state.
  • Check to see :has() work.

:has() matches an element if it contains a matching descendant: form:has(:invalid), li:has(input:checked), section:has(h2). CSS developers had wanted a parent selector since the 1990s. The reason it took 30 years is performance: browsers must recalculate styles any time a descendant changes, which requires solving difficult invalidation problems. Chrome 105, Safari 15.4, Firefox 121.

:has() snippet
2023 oklch() and the new color spaces.

oklch(), oklch(), display-p3, lab(), lch() and other wide-gamut color spaces ship in all major browsers. OKLCH's L (lightness), C (chroma), H (hue) axes are perceptually uniform: adjusting L actually changes perceived brightness, not just a mathematical value. You can build a design system palette by locking H and C and varying L. The result is visually consistent where HSL is not.

oklch() snippet
2024 @starting-style. Entry animations without JavaScript.

@starting-style defines the style an element should transition from when it first appears in the DOM. Before this, entry animations required JavaScript to add a class after insertion. Now: @starting-style { opacity: 0; } triggers a CSS transition on the element's first render. Useful for dialogs, tooltips, and anything that appears dynamically.

@starting-style snippet
2025 CSS if(). Inline conditionals in CSS values.

CSS if() allows conditional values inline: color: if(style(--variant: danger): red; else: blue). It brings branching logic into CSS values without needing separate classes or multiple rules. Combined with custom properties, it means a component can express its own variant logic in CSS. Currently shipping in Chrome Canary and behind flags. Baseline status: limited.

New CSS drops.

Join 600+ readers who've survived clearfix hacks.

ESC