
Tailwind CSS: Utility-First
Tired of naming classes? Writing CSS directly inside HTML sounds ugly, but it became the world standard. Why?

Tired of naming classes? Writing CSS directly inside HTML sounds ugly, but it became the world standard. Why?
Why does my server crash? OS's desperate struggle to manage limited memory. War against Fragmentation.

Two ways to escape a maze. Spread out wide (BFS) or dig deep (DFS)? Who finds the shortest path?

A comprehensive deep dive into client-side storage. From Cookies to IndexedDB and the Cache API. We explore security best practices for JWT storage (XSS vs CSRF), performance implications of synchronous APIs, and how to build offline-first applications using Service Workers.

Fast by name. Partitioning around a Pivot. Why is it the standard library choice despite O(N²) worst case?

That's exactly what I thought when I first saw Tailwind. A senior developer showed me some code:
<div class="flex items-center justify-between px-6 py-4 bg-white rounded-lg shadow-md">
<h2 class="text-2xl font-bold text-gray-800">Title</h2>
<button class="px-4 py-2 text-white bg-blue-500 rounded hover:bg-blue-600">
Click
</button>
</div>
"What the hell... Why is the HTML so messy?" I'm not a CS major, not a designer either, but even I could tell this looked wrong. Isn't this just inline styles with fancy names?
My senior colleague laughed and said, "Just try it. You won't go back."
I didn't understand at the time. Why would anyone abandon perfectly good CSS for this?
When I first learned web development, "Separation of Concerns" was gospel. HTML for structure, CSS for styling, JavaScript for behavior. Clean separation was the right way, they said.
So I wrote code like this:
<!-- HTML -->
<div class="card">
<h2 class="card__title">Title</h2>
<button class="card__button">Click</button>
</div>
/* CSS */
.card {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.5rem;
background-color: white;
border-radius: 0.5rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.card__title {
font-size: 1.5rem;
font-weight: bold;
color: #1f2937;
}
.card__button {
padding: 0.5rem 1rem;
color: white;
background-color: #3b82f6;
border-radius: 0.25rem;
}
.card__button:hover {
background-color: #2563eb;
}
BEM naming conventions, separate CSS files. Looks clean at first glance.
But as the project grew, problems emerged:
1. Naming Hell.card-container, .card-wrapper, .card-inner, .card-content, .card-header-section... What's the difference? Even I don't know. When I look back at my code, I think "Why did I name it this way?"
To change one button color: check the class name in HTML, open the CSS file, find the rule, modify it, go back to HTML to verify... It's like reading two books simultaneously.
3. CSS Files Growing InfinitelyEvery new component adds more CSS. I try to reuse existing styles, but they're always "similar but slightly different," so I create new ones. Eventually, the CSS file exceeds 3000 lines, and nobody knows what's used where.
4. Same Styles, Different Names.margin-top-small { margin-top: 8px; }
.mt-2 { margin-top: 8px; }
.header-spacing { margin-top: 8px; }
Same style defined three times. Someone used .margin-top-small, another person created .mt-2, and yet another made .header-spacing.
This was the trap of "Separation of Concerns." Files were separated, but the developer's mind wasn't. You still had to think about CSS and HTML simultaneously.
About three months into the project, our CSS file became so complex we decided to refactor. My senior suggested, "Let's just try Tailwind."
Initially, I resisted hard. The class names were so long, making HTML look messy.
But after one day of use, I was shocked.
I didn't create a single CSS file, yet all styling was complete.I finished the design while looking only at HTML. No file jumping. No agonizing over class names. Just write flex, items-center, px-4.
It felt like assembling LEGO blocks. Combining pre-made pieces to create the shape I wanted.
That's when I understood. Tailwind combined "the convenience of inline styles" with "the reusability of CSS."
Tailwind's core is the Utility-First philosophy. Instead of meaningful names, it provides function-based classes.
Traditional CSS thinks:
.card class".button class"Tailwind thinks:
.flex".p-4".bg-blue-500"Like atoms combining to form molecules.
Responsive design in traditional CSS:
.grid-container {
display: block;
}
@media (min-width: 640px) {
.grid-container {
display: grid;
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 1024px) {
.grid-container {
grid-template-columns: repeat(3, 1fr);
}
}
In Tailwind:
<div class="block sm:grid sm:grid-cols-2 lg:grid-cols-3">
<!-- content -->
</div>
One line. No need to write media queries directly. Just prefix with sm:, md:, lg:.
Initially, I thought "That's a lot to memorize," but patterns emerged:
sm:md:lg:xl:No more memorizing media query numbers.
Typical dark mode implementation:
.card {
background-color: white;
color: black;
}
@media (prefers-color-scheme: dark) {
.card {
background-color: #1f2937;
color: white;
}
}
In Tailwind:
<div class="bg-white dark:bg-gray-800 text-black dark:text-white">
<!-- content -->
</div>
Just prefix with dark:. It automatically detects system dark mode.
For manual toggle, in tailwind.config.js:
module.exports = {
darkMode: 'class', // use 'class' instead of 'media'
// ...
}
Then dark mode applies only when <html class="dark"> is present.
Code beats theory. Let's build a responsive card component.
Requirements:<div class="card-grid">
<div class="card">
<img src="thumbnail.jpg" class="card-image" />
<h3 class="card-title">Title</h3>
<p class="card-description">Description</p>
</div>
</div>
.card-grid {
display: grid;
gap: 1rem;
grid-template-columns: 1fr;
}
@media (min-width: 640px) {
.card-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1024px) {
.card-grid { grid-template-columns: repeat(3, 1fr); }
}
.card {
background: white;
border-radius: 0.5rem;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
transition: transform 0.2s;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.card-image {
width: 100%;
height: 200px;
object-fit: cover;
}
.card-title {
padding: 1rem;
font-size: 1.25rem;
font-weight: 600;
}
.card-description {
padding: 0 1rem 1rem;
color: #6b7280;
}
@media (prefers-color-scheme: dark) {
.card {
background: #1f2937;
}
.card-title {
color: white;
}
.card-description {
color: #9ca3af;
}
}
Tailwind:
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<div class="bg-white dark:bg-gray-800 rounded-lg overflow-hidden shadow-md hover:shadow-lg hover:-translate-y-1 transition-all">
<img src="thumbnail.jpg" class="w-full h-48 object-cover" />
<h3 class="px-4 pt-4 text-xl font-semibold text-gray-900 dark:text-white">
Title
</h3>
<p class="px-4 pb-4 text-gray-600 dark:text-gray-300">
Description
</p>
</div>
</div>
30 lines of CSS vs self-contained HTML. Which is faster?
I found Tailwind much faster. No file jumping—just work while looking at HTML.
When classes get too long, extract components.
React example:function Card({ title, description, image }) {
return (
<div className="bg-white dark:bg-gray-800 rounded-lg overflow-hidden shadow-md hover:shadow-lg hover:-translate-y-1 transition-all">
<img src={image} className="w-full h-48 object-cover" />
<h3 className="px-4 pt-4 text-xl font-semibold text-gray-900 dark:text-white">
{title}
</h3>
<p className="px-4 pb-4 text-gray-600 dark:text-gray-300">
{description}
</p>
</div>
);
}
// Usage
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<Card title="Title 1" description="Desc 1" image="img1.jpg" />
<Card title="Title 2" description="Desc 2" image="img2.jpg" />
</div>
Extract repetitive long classes into components. Reusable and clean HTML.
When creating components feels awkward, use @apply:
/* styles.css */
@layer components {
.btn-primary {
@apply px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors;
}
}
<button class="btn-primary">Click</button>
But Tailwind's official docs recommend minimal @apply use. Because it abandons Tailwind's advantages.
I only use it when truly necessary. When repeating the same style 10+ times.
Old Tailwind pre-generated all utility classes. Development CSS files grew to 3-4MB.
Since Tailwind 3.0, JIT (Just-In-Time) mode is default. It generates only needed classes on-demand.
<!-- When you write this -->
<div class="top-[117px]">
Tailwind instantly generates .top-\[117px\] { top: 117px; }.
Previously, only predefined values (top-0, top-1, top-2) were available. Now arbitrary values work:
<div class="bg-[#1da1f2]"> <!-- Twitter blue -->
<div class="w-[347px]"> <!-- Exact width from designer -->
<div class="grid-cols-[1fr_2fr_1fr]"> <!-- Custom grid -->
This was a game-changer. When designers say "This color must be exactly #1da1f2," I can use it immediately without config changes.
Every project has different design systems. Tailwind extends via config:
// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {
primary: '#5b21b6',
secondary: '#ec4899',
},
spacing: {
'128': '32rem',
'144': '36rem',
},
fontFamily: {
sans: ['Pretendard', 'sans-serif'],
},
},
},
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
],
}
Then use like this:
<div class="text-primary bg-secondary h-128 font-sans">
Define company design system in config, and team members use consistent styles. Forces prevention of weird values like "margin: 13px."
During development, all utility classes are generated. But production needs only actually used classes.
// tailwind.config.js
module.exports = {
content: [
'./src/**/*.{js,jsx,ts,tsx}',
'./public/index.html',
],
// ...
}
During build, Tailwind scans these files and includes only used classes in final CSS.
Result: Development 3MB → Production 10KB
That's Tailwind's magic. Unlimited utilities, but tiny final bundle.
Team debate: "Tailwind vs styled-components, which is better?"
styled-components camp:I've used both, and it depends on project nature:
But nowadays, most choose Tailwind. Better performance, smaller bundle, lower learning curve.
Tailwind extends via plugins:
Official plugins:@tailwindcss/forms: Form style reset@tailwindcss/typography: Markdown prose styling@tailwindcss/aspect-ratio: Aspect ratio utilities@tailwindcss/line-clamp: Text line limiting// tailwind.config.js
module.exports = {
plugins: [
require('@tailwindcss/typography'),
],
}
<!-- Apply to markdown content -->
<article class="prose lg:prose-xl dark:prose-invert">
<h1>Title</h1>
<p>Content...</p>
</article>
One prose class creates beautiful typography.
When I first saw Tailwind, I thought "What is this, how is it different from inline styles?"
But after using it, I understood. This isn't just writing CSS in HTML—it's a method to maximize productivity while enforcing design systems.
Freedom from naming hell, no more file jumping, consistent styling across team members.
Sure, there are downsides. HTML looks messy, memorizing class names is hard initially.
But once you're comfortable, there's no going back. Like when first using VS Code. Complex settings at first, but once familiar, can't use other editors.
Tailwind is now my default stack. The first library I install when starting new projects.
It came down to this: "Developer Experience" matters more than "Separation of Concerns."