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;
}
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;
}
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",
// ...
}
}
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;
}
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>
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;
}
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);
}
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 flex, items-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 (1)
"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.