CSS Portal

How Far CSS Has Come

☕
Enjoying this site?

Help keep it running with a small coffee donation. Every bit of support is appreciated!

Buy us a coffee
CSS Timeline

Today, you can centre an element in three lines, build a whole page layout with CSS Grid, and make a component that adapts to the space it's given, all without a single image, hack or line of JavaScript. It's easy to forget that none of this was always possible.

CSS is now more than 30 years old. In that time it has grown from a simple way to set fonts and colours into one of the most capable design languages around. In this post, we'll look back at where CSS came from, the struggles along the way, how far it has come, and what's on its way next.

Before CSS

The web was invented by Tim Berners-Lee at CERN in 1989, and the first websites appeared in the early 1990s. HTML described the structure of a page, such as headings, paragraphs and links, but authors had almost no control over how it looked. Browsers decided that, and some let readers choose their own fonts and colours.

Designers wanted more, so browser makers started adding presentational features straight into HTML. Tags like <font> and <center> appeared, along with attributes like bgcolor. And because HTML had no proper way to lay out a page, designers used tables, often nested several levels deep, with transparent "spacer" GIF images to push things into place.

A typical page from the mid-1990s looked something like this:

<center>
  <font face="Arial" size="6" color="#003399"><b>Welcome to my home page!</b></font>
</center>
<table width="600" cellpadding="10" bgcolor="#ffffcc">
  <tr>
    <td><font face="Arial" size="2">Thanks for visiting. Please sign my guestbook!</font></td>
  </tr>
</table>

Every heading needed its own <font> tag. Want to change your site's colour? You'd have to edit every tag, on every page, by hand. The design and the content were completely tangled together.

The birth of CSS

A proposal in 1994

On 10 October 1994, Håkon Wium Lie, who was working at CERN alongside Tim Berners-Lee, published a proposal called Cascading HTML Style Sheets. His big idea was to keep the content in HTML, and put the presentation in a separate style sheet.

The "cascading" part was just as important. Lie's proposal let styles come from several places, such as the author, the reader and the browser, and combined them according to clear rules. That cascade is still at the heart of CSS today. Bert Bos, who had been designing a style language for his own browser, soon joined the effort, and the two became the co-creators of CSS.

CSS1 and CSS2

The World Wide Web Consortium (W3C), set up in 1994 to develop web standards, published CSS Level 1 as an official recommendation on 17 December 1996. It covered the basics: fonts, colours, text alignment, margins, padding, borders and backgrounds. Even this was a revolution: the heading from the example above became one rule, which could style every heading on every page at once:

h1 {
  font-family: Arial, sans-serif;
  color: #003399;
  text-align: center;
}

p {
  font-family: Arial, sans-serif;
  font-size: small;
}

Microsoft's Internet Explorer 3, released in 1996, was the first major browser to support CSS, though only partly. CSS Level 2 followed in May 1998, adding positioning, z-index, and media types for print and other devices.

The specifications were ready. Unfortunately, the browsers weren't.

The browser wars and the hack years

Every browser did it differently

In the late 1990s, Netscape and Microsoft were fighting for control of the browser market, and each added its own features rather than carefully following the standards. Netscape Navigator 4's CSS support was notoriously unreliable. Internet Explorer measured boxes differently from the specification, adding padding and borders inside the width instead of outside it, so the same CSS produced different-sized boxes in different browsers.

There were bright spots. Internet Explorer 5 for the Mac, released in 2000, was the first browser with near-complete CSS1 support. It also introduced "doctype switching": a page's <!DOCTYPE> decided whether the browser followed the standards or copied the old, buggy behaviour. That's why we still start every page with <!DOCTYPE html> today.

The age of hacks

By the early 2000s, Internet Explorer 6 had won the browser wars, with around 90% of the market, and Microsoft then left it largely unchanged for five years. Developers coped by learning dozens of tricks to work around its bugs:

/* The "star html" hack: only Internet Explorer 6 and earlier
   matched this selector, so only they saw this rule */
* html .sidebar {
  width: 200px;
}

/* The clearfix: forcing a container to wrap around its floated children */
.clearfix:after {
  content: ".";
  display: block;
  height: 0;
  clear: both;
  visibility: hidden;
}

Layouts were built from floats, a feature originally designed to wrap text around images. Rounded corners meant slicing up four separate corner images, and a drop shadow meant another image. Web developers spent a large part of every project testing and fixing their work in each browser.

Proof that CSS could work

Meanwhile, the web standards movement was gaining ground. In 2003, Dave Shea launched the CSS Zen Garden, a site where designers restyled exactly the same HTML using only CSS. The results were wildly different, and many were beautiful. It proved to a generation of designers that table layouts were no longer needed. Firefox (2004) and Safari (2003) gave people better alternatives to Internet Explorer, and the Acid2 test (2005) challenged browser makers to get CSS right.

CSS3, phones and the rise of responsive design

From one big specification to many modules

CSS2 had been a single, large specification, which made it slow to finish. In fact, its corrected version, CSS 2.1, wasn't finalised until 2011. So for what became known as CSS3, the W3C split CSS into separate modules, such as Selectors, Colour, Backgrounds and Borders, which could each move forward at their own pace. That's also why there will never be a "CSS4": each module now has its own level number instead.

The vendor prefix era

From the late 2000s, browsers began adding exciting new features such as border-radius, box-shadow, gradients, transitions and transforms. Finally, rounded corners without images! The catch was that features usually arrived early, with a browser-specific vendor prefix such as -webkit- or -moz-, and each browser's version could work slightly differently:

.button {
  -webkit-border-radius: 8px;
  -moz-border-radius: 8px;
  border-radius: 8px;

  background: #1d4ed8; /* for browsers with no gradient support */
  background: -webkit-gradient(linear, left top, left bottom, from(#4f8ef7), to(#1d4ed8));
  background: -moz-linear-gradient(top, #4f8ef7, #1d4ed8);
  background: linear-gradient(to bottom, #4f8ef7, #1d4ed8);
}

Today, you only need the final line. (If you'd rather not write gradients or shadows by hand, our CSS gradient generator and box shadow generator will write them for you.)

Responsive web design

The iPhone arrived in 2007, and suddenly websites had to work on tiny screens as well as large ones. In 2010, Ethan Marcotte published an article called "Responsive Web Design", which showed how fluid grids, flexible images and media queries could let one site adapt to any screen size. It changed how the whole industry built websites, and it's still how we work today.

This was also the era of CSS preprocessors. Tools like Sass (2006) and Less (2009) added variables, nesting and reusable chunks of code, and compiled them into plain CSS. Many of their best ideas would later find their way into CSS itself.

The layout revolution

Flexbox

For almost 20 years, CSS had no real tool designed for page layout. That finally changed with Flexbox, which went through several drafts before working consistently in all major browsers around 2015. Things that used to need hacks, such as equal-height columns or centring something vertically, suddenly became simple:

/* Centring, the flexbox way */
.parent {
  display: flex;
  justify-content: center;  /* centre horizontally */
  align-items: center;      /* centre vertically */
}

For more ways to centre things, see our CSS centering guide.

CSS Grid

Then, in March 2017, something unusual happened: Chrome, Firefox and Safari all shipped CSS Grid within a few weeks of each other. Grid is a true two-dimensional layout system, working with rows and columns at the same time, and it made whole page layouts readable at a glance:

.page {
  display: grid;
  grid-template-columns: 220px 1fr;
  grid-template-areas:
    "header  header"
    "sidebar main"
    "footer  footer";
  gap: 24px;
}

.site-header { grid-area: header; }
.sidebar     { grid-area: sidebar; }
main         { grid-area: main; }
.site-footer { grid-area: footer; }

Compare that with the nested tables of the 1990s, or the floats and clearfixes of the 2000s. The layout is described in one place, and you can rearrange it for small screens by changing just a few lines inside a media query.

Custom properties

Around the same time, browsers added custom properties, often called CSS variables. Unlike preprocessor variables, which disappear when the code is compiled, custom properties live in the browser, so they can change on the fly, for a dark theme, inside a component, or from JavaScript:

:root {
  --brand: #1d4ed8;
  --radius: 8px;
}

.button {
  background: var(--brand);
  border-radius: var(--radius);
}

/* Change the variable, and everything that uses it updates */
.dark-theme {
  --brand: #93c5fd;
}

Internet Explorer never supported Grid properly or custom properties at all, and for years it held many developers back. Microsoft finally retired it on 15 June 2022, which opened the door to the modern era.

The modern era

Browsers working together

One of the biggest changes in recent years isn't a feature at all: it's the way browser makers now cooperate. Since 2021, the yearly Interop project has seen Apple, Google, Microsoft, Mozilla and others agree on a list of features to finish and fix together. And in 2023, the Baseline label was introduced, making it easy to see when a feature works in all major browsers. The days of each browser doing its own thing are largely over.

A flood of new features

Since 2022, long-requested features have arrived one after another:

  • Cascade layers (@layer, 2022) give you control over which groups of styles win, without fighting over specificity.
  • Container queries (2023) let a component respond to the size of its container, not just the screen.
  • The :has() selector (2023), long called the "parent selector", lets an element style itself based on what it contains.
  • Native nesting (2023) brought one of Sass's most loved features into CSS itself.
  • Subgrid (2023) lets nested elements line up with their parent's grid.
  • View transitions (2025) animate smoothly between different states of a page.
  • Anchor positioning (January 2026) attaches tooltips and menus to other elements, with no JavaScript library.
  • sibling-index() and text-box (August 2026) count an element's position among its siblings, and trim the extra space above and below text.
  • Scroll-driven animations (October 2026) link animations to the scroll position.

Modern CSS in action

Here's a small example combining three of these features: container queries, :has() and nesting. Drag the slider to change the width of the dashed container, and tick the box:

Results
560px
Mountain weekend

Two days of walking trails, lookouts and a cosy cabin.

Narrow the container below 400px and the card stacks. Tick the box and the card highlights itself. The slider only changes the container's width: both changes to the card are pure CSS.

.card-wrap {
  container-type: inline-size;   /* children can respond to this element's width */
}

.card {
  display: grid;
  gap: 12px;
  border: 2px solid #e2e8f0;

  /* Nesting: this rule only applies inside .card */
  &:has(input:checked) {         /* the card reacts to the checkbox inside it */
    border-color: #2563eb;
    background: #eff6ff;
  }
}

/* When the container (not the screen) is at least 400px wide */
@container (min-width: 400px) {
  .card {
    grid-template-columns: 120px 1fr;
  }
}

Not long ago, both of those changes would have needed JavaScript. The card's layout depended only on the screen width, and a parent element couldn't react to its children at all.

Anchor positioning is another good example. Positioning a dropdown menu next to its button, and flipping it when there isn't room, used to need a JavaScript library. Now it's a few lines of CSS:

.menu-button {
  anchor-name: --menu-button;
}

.menu {
  position: absolute;
  position-anchor: --menu-button;     /* attach the menu to the button */
  position-area: bottom span-right;   /* below it, lined up with its left edge */
  position-try-fallbacks: flip-block; /* flip above if there's no room below */
}

What we have to look forward to

CSS is moving faster than ever. These features are already working in at least one browser, and are on their way to the rest:

Masonry layouts with grid lanes

Pinterest-style "masonry" layouts, where items of different heights pack neatly into columns, have needed JavaScript for years. After a long debate about the syntax, the answer is display: grid-lanes. Safari turned it on in version 26.4, and other browsers are testing it behind flags.

.gallery {
  display: grid-lanes;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 16px;
}

Functions and conditions

CSS is gaining some of the logic that once needed a preprocessor. The @function rule lets you write your own reusable functions, and the if() function lets you choose a value based on a condition. Both work in Chrome now, and work on mixins, reusable blocks of declarations, has begun.

/* A reusable custom function */
@function --negative(--value) {
  result: calc(-1 * var(--value));
}

.pull-out {
  margin-inline: --negative(var(--gap));
}

/* An inline condition */
.button {
  background: if(style(--variant: danger): #dc2626; else: #2563eb);
}

Fully stylable form controls

The <select> dropdown has always been one of the hardest elements to style. With appearance: base-select, you can style every part of it, including the options list and the arrow. It works in Chrome, and in Safari since version 27.

select,
::picker(select) {
  appearance: base-select;  /* switch to the fully stylable version */
}

Interop 2026 and beyond

The browser makers' Interop 2026 list shows where they're focusing next. On the CSS side, it includes:

  • Container style queries, which let elements respond to the custom properties set on their container, not just its size
  • The attr() function, extended so any property can use an HTML attribute's value
  • contrast-color(), which automatically picks a readable text colour for a given background
  • The shape() function, for drawing responsive custom shapes with CSS
  • more work on anchor positioning, view transitions and scroll-driven animations, so they behave the same everywhere

Further out, there's work on things like random() values, margin-trim, and new corner-shape options for more than just rounded corners. If the last few years are anything to go by, many of these will be ready sooner than you'd think.

Using new features safely: you don't have to wait for every browser before trying something new. Many features can be added as a progressive enhancement: the page works fine without them, and gets better in browsers that support them. The @supports rule lets you check for a feature before using it.

CSS at a glance

Here are some of the key moments in CSS's history:

YearMilestone
1994Håkon Wium Lie proposes Cascading HTML Style Sheets
1996CSS Level 1 becomes a W3C recommendation; Internet Explorer 3 adds partial support
1998CSS Level 2 adds positioning and media types
2000Internet Explorer 5 for Mac is the first browser with near-complete CSS1 support
2003The CSS Zen Garden shows what CSS-only design can do
2005The Acid2 test challenges browsers to follow the standards
2010"Responsive Web Design" popularises media queries
2011CSS 2.1 is finally completed
c. 2015Flexbox works consistently across major browsers
2017CSS Grid ships in major browsers within weeks
2022Internet Explorer is retired; cascade layers arrive
2023Container queries, :has(), nesting and subgrid; Baseline is introduced
2026Anchor positioning and scroll-driven animations work in all major browsers

From <font> tags and spacer GIFs to container queries and anchor positioning, CSS has come an incredibly long way. Many of the things we do without thinking today, such as rounded corners, responsive layouts and centring, once took images, hacks or JavaScript. And the best part? There's plenty more to come.

Want to explore everything CSS can do today? Browse our CSS properties reference, and check your code with our CSS validator.

☕
Enjoying this site?

Help keep it running with a small coffee donation. Every bit of support is appreciated!

Buy us a coffee