Back to Blog
5 min. read

#2 Web Styling Time Travel: What Is Making Tailwind Top 1 Today?

In the previous part of this article, we went through the 4 steps preceding the appearance of the groundbreaking Tailwind CSS. I invite you to explore the next steps that will take us all the way up to the arrival of this constantly evolving and opening new possibilities framework.

Stage 5: Encapsulation with CSS Modules

CSS Modules emerged around 2015, introduced by Glen Maddern and Mark Dalgleish to enable scoped styles in modular front-end applications. Tobias Koppers and Johannes Ewald played a key role through their work on Webpack, which made CSS Modules practical by bundling styles modularly.

This approach let developers import CSS files directly into JavaScript, bundle styles separately, or inject them into a style tag. Classes could be referenced via classnames.nameOfClass, with automatic transformations applied if configured.

Encapsulation was achieved by generating unique class names, like dasda-dad-dasd-123, ensuring styles remained isolated in each build. Here’s an example:

										/* button.module.css */
.primary {
  background-color: blue;
}

									
										// In a "button.jsx" React file
import styles from './button.module.css';

function Button() {
  // In code, "styles.primary", but after the build, it becomes "dasda-dad-dasd-123"
  return <button className={styles.primary}>Click me</button>;
}

									

By combining CSS Modules with preprocessors, developers started using .module.scss files to leverage preprocessor features while maintaining encapsulated styles.

At the same time, TypeScript had matured, impressing developers with its capabilities. Someone (exactly who is unclear) introduced type generation for CSS Modules via a Webpack plugin. This allowed TypeScript to perform static checks when importing classes, catching typos and incorrect class usage automatically. This approach became known as Typed CSS Modules, further enhancing the reliability of CSS in TypeScript projects.

										/* button.module.scss */
.primary {
  background-color: blue;
}

									
										// In a "button.tsx" React file 
import styles from './button.module.scss';

function Button() {
  // TypeScript throws an error 💢 due to the typo
  return <button className={styles.pimarlyms}>Click me</button>;
}
									

For 1–2 years, Typed CSS Modules gained traction at conferences and in new projects. It offered encapsulation, worked well with preprocessors, and was combined with methodologies. It seemed like the perfect solution — until new challenges arose.

Stage 6: The Impact of CSS-in-JS

All of this stemmed from a single presentation — Christopher Chedeau (vjeux) from Facebook in his 2014 talk, “CSS in JS”. It’s hard to determine who originally came up with the idea, but it’s definitely worth acknowledging that he played a significant role in its popularization.

As component-based frameworks like React and Vue gained popularity, CSS-in-JS emerged as a new way to address the challenges of styling within a component-centric architecture.

The question arose: “Can I use CSS syntax inside React components while maintaining the same level of composition and encapsulation as React component logic?” The answer was yes! This became easier to implement thanks to tagged template literals (introduced in ECMAScript 2015).

										// This is what you write with styled-components or similar CSS-in-JS libraries
const StyledButton = styled.button`
  background-color: ${props => props.primary ? "blue" : "gray"};
  color: white;
  padding: ${props => props.size === "large" ? "10px 20px" : "5px 10px"};
`;

									
										// This is roughly what happens under the hood when the template literal is processed
const StyledButton = styled.button(
  ["background-color: ", ";\n  color: white;\n  padding: ", ";\n"],
  props => props.primary ? "blue" : "gray",
  props => props.size === "large" ? "10px 20px" : "5px 10px"
);

									

Imagine doing it explicitly in the way from the second snippet. Total nightmare to maintain…

The goal was to merge structure, styling, and logic into a single, fully encapsulated component, challenging the traditional separation of HTML, CSS, and JS. The community focused on coding aspects like maintenance and encapsulation but often overlooked the performance impact of early CSS-in-JS solutions, which added significant JavaScript to the bundle. This led to poor Lighthouse scores on larger pages. I personally saw this when I switched greenonsoftware.com from CSS-in-JS (styled-components) to plain CSS, boosting the performance score from 54% to 92%.

This lesson sparked improvements in CSS-in-JS, with new versions focusing on reducing JavaScript and generating static styles. To understand CSS-in-JS evolution, let’s dive into its history and the solutions developed along the way.

React’s Arrival in 2014 and Inline Styles

It’s strange, but this period is worth mentioning. In the early stages of React, developers aimed for the best encapsulation by defining inline styles. Anything that needed to be reused was provided via globally exported objects… It was a nightmare!

										const styles = {
  container: {
    padding: '20px',
  },
};

function Component() {
  return (
    <div style={styles.container}>
      <h1>Hello World</h1>
    </div>
  );
}
									

This approach had major limitations, including the inability to use media queries, pseudo-selectors, or keyframe animations (just a few of the many issues), along with other drawbacks.

Radium in 2014: A Basic Version of CSS-in-JS

The previously mentioned React codebase was far from ideal, leading to the creation of real tools. One of the first was Radium, which extended inline styles to support pseudo-selectors and media queries, while still using JavaScript objects for styling.

										import Radium from 'radium';

const styles = {
  button: {
    ':hover': {
      backgroundColor: 'darkblue'
    },
    '@media (max-width: 768px)': {
      padding: '12px'
    }
  }
};

function Button() {
  return <button style={styles.button}>Click Me</button>;
}

									

Modern CSS-in-JS: Styled-Components and Emotion in 2016–2017

Styled Components and Emotion marked an ongoing paradigm shift, allowing developers to write actual CSS syntax within JavaScript. It was a breath of fresh air.

										import styled from 'styled-components';

const Button = styled.button`  
  &:hover {
    background-color: darkblue;
  }
  
  @media (max-width: 768px) {
    padding: 12px;
  }
`;

function Component() {
  return <Button>Click Me</Button>;
}
									

Both solutions introduced modern syntax and great possibilities. While some argued these technologies were unnecessary, many teams adopted them. However, many soon regretted their decision. The runtime impact of CSS-in-JS was huge, especially when developers applied it to every JSX node, leading to a bloated and inefficient styling codebase.

										const ButtonContainer = styled.div`
  display: flex;
`;

const Button = styled.button`
  padding: 10px 20px;

  &:hover {
    background-color: #0056b3;
  }
`;

const ButtonLabel = styled.span`
  font-weight: bold;
`;

// Usage
const App = () => {
  return (
    <ButtonContainer>
      <Button>
        <ButtonLabel>Click Me</ButtonLabel>
      </Button>
    </ButtonContainer>
  );
};

									

This doesn’t mean it’s a bad solution. The real issue was that developers often used it without much thought — especially when they were tired. Encapsulation is great until it isn’t — sometimes, it’s just not necessary. The same code could have been easily implemented using the OOCSS methodology and plain CSS, but some developers were too rigid, insisting on following the new trend 100%.

StyleX by Facebook in 2020

StyleX emerged to address the pain point of the previously mentioned huge runtime overhead.

										import React from 'react';
import { stylex } from 'stylex';

// Define styles
const buttonStyles = stylex.create({
  base: {
    padding: '10px 20px',
  },
  primary: {
    backgroundColor: '#007bff',
  },
});

const Button = ({ children }) => (
  <button className={stylex(buttonStyles.base, buttonStyles.primary)}>
    {children}
  </button>
);

									

StyleX aimed to balance dynamic styling creation at runtime with avoiding bloated performance and excessive JavaScript generation. It was a compromise — providing encapsulation while prioritizing performance over encapsulation at all costs.

Vanilla-Extract in 2021: Zero Runtime Overhead

Leveraging TypeScript and lessons from previous approaches, the authors of Vanilla-Extract went all-in on achieving zero JavaScript runtime overhead. The styling codebases are fully transpiled into CSS during the build phase.

										import { style } from '@vanilla-extract/css';

const button = style({
  // ...
  cursor: 'pointer',
  ':hover': { backgroundColor: '#0056b3' },
});

const Button = ({ children }) => (
  <button className={button}>{children}</button>
);
									

Vanilla-Extract offers a zero-runtime solution by shifting all style processing to the build phase, ensuring no additional JavaScript impact on runtime performance.

Other CSS-in-JS solutions, like Linaria and Stitches, are also similar. I’ll just mention them to save space in this article.

Stage 7: The Arrival of Tailwind CSS (History Made a Circle)

More about history: Who crafted Tailwind?

Tailwind CSS emerged in 2019 as a response to the growing complexity and verbosity of traditional component-based frameworks. By using utility classes within a structured design system, it addressed several key challenges:

  1. Eliminated the runtime overhead of CSS-in-JS.
  2. Offered greater flexibility than component-based frameworks like Bootstrap.
  3. Reduced reliance on custom CSS compared to methodologies like BEM.
  4. Enforced design consistency through a configurable system of values (design tokens).
  5. Provided an easy-to-use styling layer.
  6. Maximized performance and speed of delivery.

Its popularity surged alongside the rise of AI tools like ChatGPT and the broader OpenAI ecosystem. Seamless integration with modern tools like V0 from Vercel further fueled its widespread adoption.

Many platforms also began offering strong support for Tailwind, including design tools like Figma and various low-code platforms. Tailwind’s simplicity made it an excellent fit for AI-assisted workflows, reducing the need for complex reasoning and minimizing context switching.

Eventually, IDE extensions enhanced the developer experience. This led to a modern approach for crafting UIs quickly, efficiently, and in a “good enough” way. By reusing mostly pre-written code and making basic customizations, developers could build interfaces at lightning speed!

										// tailwind.config.js
module.exports = {
  content: ['./src/**/*.{html,js,jsx,ts,tsx}'],
  theme: {
    extend: {},
  },
  plugins: [],
}

									
										/* .main.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

									
										const Button = ({ children }: { children: React.ReactNode }) => (
  <button className="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-700 transition-colors">
    {children}
  </button>
);

									

Clean code gurus started to yield again. It’s hard to understand why people struggle with the basic principle of programming and real-life rules: you can’t have everything. Instead of getting stuck in endless debates about conventions, paradigms, or class names — just install Tailwind.

Yes, this framework is designed to give you almost everything you need, following design principles through tokens, while allowing customization where necessary. It’s not a dictator — it gives you the freedom to write simple CSS, use a pre-processor, or even merge it with a CSS-in-JS approach if you need more encapsulation.

All features and pros/cons of Tailwind are covered in my article Tailwind is a Real Game Changer.

Interestingly, newer tools like UnoCSS and WindiCSS have emerged. We’ve seen similar iterations in CSS-in-JS and other methodologies — people keep refining ideas. However, I’d be cautious when using them; they have decent popularity but remain somewhat niche.

Answering the Question: Why is Tailwind Top 1?

BECAUSE DEVELOPERS ARE TIRED AND WANT THINGS TO GET DONE ASAP IN A GOOD ENOUGH WAY – that’s my bold statement, and it’s 100% legitimate.

But seriously, as we’ve seen, time changes, people change, and requirements change. Tailwind has successfully adapted to the modern needs of web development. While it’s true that technology is constantly evolving and developers sometimes change their preferences (often based on new trends or personal mistakes), Tailwind has carved out its place in the development landscape for several reasons. Here’s why I believe Tailwind is currently #1:

  1. Focused on Good Enough Performance: Tailwind strikes a balance between performance and functionality. Unlike some alternatives, it doesn’t burden your projects with unnecessary overhead, and it delivers solid results without compromising speed.
  2. Allowing Flexibility: Tailwind gives developers the freedom to customize and control their styles, allowing them to adapt the framework to suit their specific needs. It’s not restrictive, unlike some opinionated frameworks.
  3. Enforcing Design Principles and System Conventions: Tailwind is built around a design system that includes a set of principles and values that ensure design consistency across projects. Its utility-first approach ensures that every piece of styling aligns with these principles.
  4. Providing Out-of-the-Box Resets, Components, and Utility Classes: Tailwind includes a vast set of utility classes that speed up development. Additionally, it provides ready-to-use resets and components that save time on foundational design work.
  5. Focused on Delivery Speed: Tailwind’s design is geared toward rapid prototyping and production. By simplifying the development process with pre-defined utility classes and integration with modern tools, it allows teams to build and deploy projects faster than many alternatives.
  6. Huge Ecosystem: Tailwind’s ecosystem is robust and continues to grow. Tools like ShadCN, Headless UI, and countless others complement the core framework, expanding its capabilities and making it even more powerful for developers.

While technology trends will continue to evolve, these foundational features make Tailwind a strong contender in web development today.

Summary & Conclusions

Please, please, and please… In current software development, we need to be pragmatic. All of the mentioned techniques, technologies, and tools come with different sets of pros, cons, and features that should be carefully considered before choosing something for your project.

By walking through the historical context, I’ve shown how things have changed over the years in web development, and I can guarantee that it will change again. It’s just the natural law of being a web developer. There’s even a formula (crafted by me) that proves it’s true:

										2024 Web Developer = 
    2023 Web Developer + 
    1 New Framework + 
    3 New Tools + 
    2 New Design Systems + 
    5% More Coffee

									

The key takeaway here is that you should always be pragmatic, fact-check, and constantly experiment. My default choice is Tailwind, for the reasons I shared with other Tailwind fans. I have a limited amount of time and want to save brainpower for more advanced topics instead of constantly figuring out how to start a new project with newly developed conventions.

You might share the same perspective or a different one, but that’s the beauty of our profession — we can still express our opinions freely. And I hope that never changes.