DEV Community

Fabian Frank Werner
Fabian Frank Werner

Posted on • Edited on

CSS vs Tailwind CSS - I built the same home page with both

If you go to any social media right now, I promise you will find developers screaming at each other about CSS.

On one side, you have the Purists. They believe in the sanctity of semantic HTML, the Separation of Concerns, and the "Cascading" nature of style sheets.

On the other side, you have the Pragmatists. The Tailwind cult. They believe that naming classes is a waste of time and that CSS is better when it looks like a crossword puzzle that exploded in your HTML.

But arguments are cheap. Code is real. So, to actually understand the difference, I built the exact same project with both. We are recreating the Google Dark Mode homepage. It looks simple—a logo, a search bar, a footer—but it’s actually a perfect stress test for layout systems, theming, and component architecture.

We aren't doing a "Winner takes all" battle today. We are going to dissect the experience of building this. We’ll look at the setup, the mental models, the ugly parts of the code, and finally, a file-size comparison that actually shocked me. By the end, you’ll know exactly why you should pick one over the other. Let's get into it.

Chapter 1: The Setup

Let’s start with the setup. This is where the first philosophical divide happens.

With Vanilla CSS, the "stack" is non-existent. You create a file. You link it. You write code. There is zero friction. You are writing the language the browser actually understands.

Tailwind, however, brings the baggage. You need Node.js. You’re initializing a config file. You’re setting up a build process to watch your files. For a single HTML page like this, Tailwind feels like bringing a construction crane to build a Lego set.

But—and this is a big but—once that crane is set up, the speed changes. In Vanilla CSS, you are working in the Global Scope. The "Cascade" is powerful, but it's also dangerous. If I define a generic button class, it affects every button on the site.

Tailwind doesn't rely on the cascade. It relies on a System. And that system starts with configuration.

Chapter 2: The Reset

But before we draw a single pixel, we have to talk about the "Reset." This is Step 0, and it catches almost everyone off guard.

In standard CSS, the browser uses a default box model where adding padding increases the width of the element. If you have a 100px box and add 20px of padding, it becomes 140px wide. It makes layout math a nightmare.

So, in my Vanilla CSS version, the very first thing I have to do is manually tell the browser: "Stop trying to help me." border-box ensures that if I say a box is 100px, it stays 100px, even if I stuff it with padding.

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
Enter fullscreen mode Exit fullscreen mode

In Tailwind, I didn't write a single line of reset code. Why? Because Tailwind injects a system called "Preflight" automatically. It flattens the browser styles, applies border-boxglobally, and removes all default margins. It gives you a truly blank canvas. In Vanilla CSS, you have to build that canvas yourself.

Chapter 3: The Colors

Now, let's talk about Theming. We need to match Google's specific dark mode grays.

In my Vanilla CSS build, I use CSS Variables.

:root {
  --bg-primary: #1f1f1f;
  --bg-element: #303134;
  --link-color: #8ab4f8;
}
Enter fullscreen mode Exit fullscreen mode

This is clean. It’s dynamic. If I change --bg-primary to red in the DevTools, the whole site updates instantly. But, there are no guardrails. As a developer, I can easily break the design system. So the CSS file allows me to be sloppy.

In Tailwind, I have to be more disciplined. I open tailwind.config.js and extend the theme.

colors: {
  google: {
    bg: "#1f1f1f",
    element: "#303134",
    // ...
  }
}
Enter fullscreen mode Exit fullscreen mode

This config file acts as a "Contract." When I go to my HTML, IntelliSense kicks in. If I type bg-goo..., it suggests bg-google-bg. It prevents "Magic Numbers." You aren't just painting pixels; you are referencing a system. For large teams, this is the difference between a consistent UI and a messy one.

Chapter 4: The Layout

Now, let's look at the actual layout code. This is where we see the concept of "Context Switching."

In Vanilla CSS, I have this markup (<div class="nav-right">) that reads like English. But as a developer, I don't know what .nav-right does. Is it a grid? Is it absolute positioning? To find out, I have to switch tabs to style.css and find the class.

.nav-right {
  display: flex;
  align-items: center;
}
Enter fullscreen mode Exit fullscreen mode

Okay, so this is Flexbox. We are aligning the items along the cross-axis. In Vanilla CSS, the structure and the layout logic CSS are physically separated. You have to hold the connection in your head.

Now look at Tailwind. <div class="nav-right flex items-center gap-4"> I admit, it looks "noisy." But look at gap-4. In the old days of CSS, spacing items was hard. We used margin-right on everything except the last child. Tailwind exposes the modern CSS gap property as a utility. gap-4 puts 1rem of space between every child. I can visualize exactly how this component behaves just by reading the class string. This is "Locality of Behavior." I am styling the element while I build it.

Chapter 5: The Problem

Now, I hear the Backend Developers screaming at the screen: "But what if I have a button? Do I have to copy-paste bg-blue-500 text-white rounded px-4 py-2 on every single button? That violates DRY principles!" I hear you! Now, this is the "Google Search" buttons. We have two of them.

In my Tailwind HTML, yes, it looks repetitive.

<button class="bg-google-element hover:border-gray-500 ...">Google Search</button>
<button class="bg-google-element hover:border-gray-500 ...">I'm Feeling Lucky</button>
Enter fullscreen mode Exit fullscreen mode

If I want to change the padding on both, I have to edit two places. This sucks.

But Tailwind has a solution for this called @apply. If you really want to clean this up, you can go to your input CSS file and do this:

.btn-google {
  @apply bg-google-element text-google-textMain px-4 py-2 rounded hover:border-gray-500;
}
Enter fullscreen mode Exit fullscreen mode

Now, in your HTML, you just use class="btn-google". So you can use classes in Tailwind. But usually, you shouldn't. In modern development (like with React, Vue, Svelte), you wouldn't make a CSS class. You would make a Component. You’d make a <Button /> component that holds these styles internally. Therefore Tailwind works best when paired with a component framework, not just raw HTML.

Chapter 6: The Specifics

Let's get technical. The Search Bar is the most complex component because it has specific pixel dimensions and hover states.

In CSS, I handle the hover state using a Pseudo-class.

.search-container:hover {
  background-color: var(--bg-element-hover);
  box-shadow: 0 1px 6px 0 rgba(23, 23, 23, 0.5);
}
Enter fullscreen mode Exit fullscreen mode

This relies on Specificity. The browser calculates that this rule is more specific than the base rule, so it applies it.

In Tailwind, we do this inline. And this is where we see a feature called Arbitrary Values. Look at this class: h-[46px]. And this one: shadow-[0_1px_6px_0_rgba(23,23,23,0.5)]. Tailwind haters love to point at this and say, "See! That's ugly!"

And yeah, it is ugly. But it’s also incredibly powerful. I needed a 46px height to match the Google reference. I didn't have to create a new class or add an inline style attribute. I just told the Tailwind Just-In-Time compiler: "Hey, make me a class for 46 pixels." And it did. I didn't have to worry about specificity wars or overriding other styles. It just works.

Chapter 7: The Compiler

Speaking of the compiler, let's get "Under the Hood." How does Tailwind actually work? When I type a class like h-[46px] for the search bar, what is happening?

Tailwind is not a static CSS file. It is a program written in JavaScript. When you save your HTML file, the Tailwind "Just-In-Time" (JIT) compiler scans your code. It uses Regular Expressions (Regex) to look for class names. It sees h-[46px]. It parses that string, realizes you want a height of 46 pixels, and generates a CSS rule on the fly: .h-\[46px\] { height: 46px; }

This is why you can leave your CSS file empty. The compiler is writing the CSS for you, based on what you asked for in the HTML. It also handles Specificity for you. In Vanilla CSS, if I have an ID and a class, they fight. In Tailwind, because everything is a utility class, everything has the same "weight." You rarely run into those moments where you write a style and nothing changes because some hidden rule is overriding it.

Chapter 8: The Paradox

Okay, we have two identical looking websites now. But what are we shipping to the user? We always hear that "Tailwind is small." So I ran the build, and here are the results…

Vanilla CSS: 5 KB. Tailwind Output: 16 KB.

Wait. The "optimized" tool is 300% larger? Yes, and here is the nuance. Tailwind's 16KB includes that "Preflight" reset we talked about in Chapter 2. It’s a reset sheet that normalizes all the browser quirks so your site looks the same on Chrome and Safari. That’s a base cost. Vanilla CSS is "Pay for what you use." I only wrote the lines I needed, so it’s tiny.

However, this is a single page. If I added 50 more pages to this app... The Vanilla CSS file would grow Linearly. Every new page means new classes. The Tailwind file would flatten out (Logarithmic). Why? Because I’m reusing flexitems-center, and text-white everywhere. So while Vanilla wins the sprint, Tailwind wins the marathon.

Chapter 9: The Verdict

So, which one should you choose?

If you are a beginner, look me in the eyes: Do not start with Tailwind. You need to feel the pain of the Box Model. You need to understand how Flexbox axes work. You need to understand position: relative vs absolute. If you jump straight to Tailwind, you are learning a framework, not the web.

But, if you are building a real product, a dashboard, or working on a team? Tailwind CSS has won me over. The "ugliness" of the HTML is a small price to pay for the development speed. It allows you to come back to a project 6 months later and know exactly what is happening without hunting through a 5,000-line stylesheet.

And if you want to see me compare React vs Svelte or something else entirely, let me know in the comments. Don't forget to cursor: pointer that like button. See you in the next one.

Top comments (17)

Collapse
 
maxart2501 profile image
Massimo Artizzu • Edited

If you are a beginner, [...] do not start with Tailwind.

Good, good! May I add some lines?

  • If you are mid level, do not start with Tailwind.
  • If you are a senior, do not start with Tailwind.
  • If you are a guru, do not start with Tailwind.

In short, do not start with Tailwind ever, period.

You call CSS users "purists" versus Tailwind's "pragmatists", but let's see what Tailwind's adoption means pragmatically:

  • of course, readability goes down the drain;
  • for every .css files that goes from 150 kb to 40, every html page goes up from 20 kb to 40;
    • and don't start with "HTML can be compressed very well": CSS can too;
    • CSS files are aggressively cached by browsers, HTML are not.
  • it's never Tailwind alone: here comes a plethora of plugins and packages, from your IDE to runtime, that you have to install just to get a grip on the mess;
  • debugging with dev tools becomes an awful experience;
  • RUM data becomes gibberish...

I can go on for hours.

Collapse
 
fabianfrankwerner profile image
Fabian Frank Werner

Please do, I'm interested in what's left :)

Collapse
 
maxart2501 profile image
Massimo Artizzu • Edited

I could honestly write a whole series on the matter, but I fear it could unleash mean reactions from the frontend community.

  • Tailwind fans often show how they get the best from LLMs because they answer using Tailwind basically by default. Problems arise when a new major version of Tailwind comes out, and AI will still give answers based on the old version for months. It already happened.
  • The aid you get from fast prototyping gives the false impression that everything can be assimilated as a fire-and-forget project, thus neglecting maintainability.
  • When you take zero effort in naming a class, you don't think about what meaning or purpose an element has: this seems liberating at first, but in the end this is what happens:
    • we stop thinking about the meaning not only of classes, but of attributes and tag names. Everything becomes a <div>, and DOM size and accessibility suffer;
    • we carry long strings of Tailwind class names around in our runtimes because what we have left is merely a presentational effect, and not a meaning anymore.
  • Wanna learn from the masters? Tailwind's own website is a disaster, where you can find class names several thousand characters long because they embed a freaking PNG in base 64! Nobody really know what to do with Tailwind.
  • This is crystal clear when so many devs defy one of the main advantages of using utility classes: not having to name things. Instead, so many use @apply, others pack class names together in "variants" (which you have to name).
  • And in the latter case, it all happens at runtime, which is something we loathed so much in those CSS-in-JS libraries, but now it's acceptable somehow.
  • By the way, @apply will conflict with the upcoming native CSS mixins: which means Tailwind will prove itself to be not future-proof yet again, after Tailwind 3 inability to use @layer freely.
  • Not to mention having to wait for a new major release just to support a new native CSS syntax, which is doubly ludicrous as a feature could be available for months before you could use it, and has been conceived by renowned tech experts and browser representatives, instead of Adam Wathan's capricious unvetted ideas.
  • The result of short-term gain and a lot of advertising has created the most toxic community of klout chasers around something so foundational like CSS, it's honestly disgusting. I swear I've seen the exact same post about a "revolutionary technique" with Tailwind posted on LinkedIn by some self-appointed "senior full-stack development architect" or whatever that's actually a copy of a post by David Khourshid on Twitter.
  • And apropos of that, Tailwind's bracket syntax is a giant middle finger to whoever conceived CSS as a structured language to express complex relationships, to the consolidated best practice to avoid magic numbers, and to the usual convention of having a specificity of 0.1.0 for utility classes.
  • Let's not forget classes have been conceived more than a quarter of a century ago, when we didn't have anything better: now we do and we can be more expressive and effective without using classes altogether (I've been doing this for years now).

Enough?

Thread Thread
 
fabianfrankwerner profile image
Fabian Frank Werner

Thank you for taking the time! With such strong insightful opinions I would love if you published more posts, even reasonable rants are very entertaining content. Do you have an active blog or newsletter I can check out?

Collapse
 
alohci profile image
Nicholas Stimpson

"The Vanilla CSS file would grow Linearly." Only if you want every page to look entirely different from every other page on your web site, or you're really bad at choosing class names.

On any normal site, if the class names are well-chosen to reflect the semantic domain space of the site's content, many of the classes you use on your home page are going to be reused across all the other pages. The second page you create will add a few extra classes and some of these too will be used across many subsequent pages. The growth curve will be a very similar shape to Tailwind's.

Collapse
 
fabianfrankwerner profile image
Fabian Frank Werner

Thanks for pointing that out. In the end it comes down to personal preference, I guess...

Collapse
 
tracygjg profile image
Tracy Gilmore

That is not quite true.
CSS is a standard that is widely applicable and will be for years to come.
Tailwind, a good as it is, is a skill that might not be applicable in your next project and will some day be replaced by the next shiny new tool.
Besides, each additional tool or library is project bloat. Standard web technologies are built into the browser.
This also applies to Fetch API v Axios, and I really like Axios.

Thread Thread
 
fabianfrankwerner profile image
Fabian Frank Werner

True!

Collapse
 
spo0q profile image
spO0q • Edited

Tailwind is a system!

It's definitely not meant for beginners. Sharp tool!

A great use case, though, is when you need a similar system. In this case, it's probably better to rely on this tool than to do it yourself, but even in this case, be aware it's also designed for modern browsers.

Collapse
 
fabianfrankwerner profile image
Fabian Frank Werner

True!

Collapse
 
appurist profile image
Paul / Appurist

Sorry, but the bias on this is obvious. Phrases like "I didn't have to create a new class" hide how simple it is. And your HTML isn't polluted like it is with Tailwind. Take a look at "Why Nue" and remember web development when it was actually web development, not investing a whole learning slope for something that the web doesn't actually use.

Collapse
 
fabianfrankwerner profile image
Fabian Frank Werner

Will check out Nue!

Collapse
 
itsugo profile image
Aryan Choudhary

Love the nautical-themed comparison! Tailwind CSS might be the trusty compass that keeps your design on course, but Vanilla CSS is the open sea where you chart your own course and learn the ropes. Can't help but wonder about how you handle the trade-off between file size and project complexity in your own work?

Collapse
 
fabianfrankwerner profile image
Fabian Frank Werner

Nice ai response

Collapse
 
118m8 profile image
118M8

Good for use

Collapse
 
fabianfrankwerner profile image
Fabian Frank Werner

🫡

Collapse
 
neurabot profile image
Neurabot

Congrats.