Tailwind CSS: Why I Use a Utility-First Framework for This Blog
Leroy - Jun 1, 2026 - 8 min read
The Framework I Almost Skipped
For years, I wrote vanilla CSS. It worked fine. Then I tried Bootstrap, which felt heavy. Then CSS-in-JS, which felt clever but added complexity. I assumed Tailwind was just another trend - until I actually used it.
Now it's the foundation of this blog's design. Here's why.
What is Tailwind CSS?
Tailwind is a utility-first CSS framework. Instead of writing custom CSS classes like .card or .btn-primary, you compose styles directly in your HTML using small, single-purpose utility classes:
<div class="flex items-center gap-4 border rounded-lg p-4 bg-card">
<p class="text-sm text-muted-foreground">Hello</p>
</div>
Each class does one thing:
flex- setsdisplay: flexitems-center- aligns items verticallygap-4- addsgap: 1remborder- adds a1px solidborderrounded-lg- rounds the cornersp-4- addspadding: 1rembg-card- sets the background to the card color variable
No naming things. No context switching between HTML and CSS files. No specificity wars.
Why I Use Tailwind
1. No Name Fatigue
The hardest part of CSS is naming things. Is it .card or .post-card or .article-card? What about the variation with an image - .card-with-image? .media-card?
With Tailwind, you don't name anything. You just write the styles inline. This sounds messy, but in practice it means I spend zero time thinking about class names and 100% of my time thinking about the actual design.
2. Consistent Design Tokens
Tailwind enforces a design system by default. You don't write #333 or 14px in random places. Every value comes from a predefined scale:
/* Instead of: */
font-size: 14px;
margin-top: 12px;
/* You write: */
text-sm
mt-3
This means the spacing and typography are consistent across the entire site without thinking about it. The text-sm utility always means 0.875rem (14px). The mt-3 always means 0.75rem (12px). No magic numbers.
3. Custom Theme Variables
Tailwind v4 lets you define custom CSS variables and map them into the framework:
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-accent: var(--accent);
}
These become utility classes I use everywhere:
<div class="bg-card text-foreground">...</div>
<span class="text-accent">Highlighted text</span>
The values come from CSS custom properties that change between light and dark mode:
:root {
--accent: oklch(0.88 0.01 260); /* soft blue */
}
.dark {
--accent: oklch(0.3 0.06 280); /* muted indigo */
}
One class, bg-accent, works in both themes. No media queries needed.
4. Dark Mode Without the Pain
Dark mode with vanilla CSS usually means duplicating styles or writing verbose @media (prefers-color-scheme: dark) blocks. With Tailwind, I toggle a .dark class on the <html> element, and all the custom properties swap automatically:
function toggleTheme() {
document.documentElement.classList.toggle('dark');
}
Every utility that uses a custom property - bg-card, text-foreground, border-border - instantly adapts. No extra CSS. No media queries.
5. Responsive Design Without Media Queries
Tailwind's breakpoint prefixes let me write responsive layouts inline:
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
<!-- 1 column on mobile, 2 on tablet, 3 on desktop -->
</div>
This is one line of HTML instead of 10+ lines of CSS with @media queries. The breakpoints (sm, md, lg) are consistent across the entire codebase.
6. The Typography Plugin
This blog uses Tailwind's typography plugin (@tailwindcss/typography) which styles raw HTML content from Markdown rendering:
<section class="prose lg:prose-xl">{{ .Content }}</section>
One class - prose - gives me beautiful typography for blog posts: proper heading hierarchy, readable line heights, styled blockquotes, and code blocks. I override specific tokens for my theme:
:root {
--tw-prose-body: var(--foreground);
--tw-prose-headings: var(--foreground);
--tw-prose-links: var(--primary);
}
The blog posts on this site are Markdown files rendered to HTML by Goldmark (a Go library). The prose class makes them look like they were hand-crafted.
7. Small Production CSS
This is the argument that surprised me. People assume utility classes mean bloated CSS. But Tailwind uses PurgeCSS under the hood - it scans your HTML files and only includes the classes you actually use:
@source "../../templates/";
The compiled style.css for this blog is under 15KB gzipped. That's smaller than most hand-written CSS files. No unused styles, no bloat.
8. v4 is CSS-First
Tailwind v4 moved configuration from tailwind.config.js into CSS itself:
@import "tailwindcss";
@plugin "@tailwindcss/typography";
@custom-variant dark (&:is(.dark *));
@theme inline {
--font-sans: "Manrope", sans-serif;
}
This means there's no configuration file to maintain. The CSS file is the single source of truth for the design system. No YAML, no JSON, no JavaScript config - just CSS.
9. It Works with Any Backend
Tailwind is just CSS. It doesn't care about your framework. This blog uses Go's html/template on the backend, and Tailwind works perfectly because it's purely a CSS pipeline:
bun run css:build
# Runs: tailwindcss -i ./static/css/input.css -o ./static/css/style.css
The output is a static CSS file served by Go's http.FileServer. No JavaScript bundler needed. No framework lock-in. No build step in the hot-reload loop.
10. A Natural Fit for Component Frameworks (React, Svelte, Vue)
This blog uses raw HTML templates, but Tailwind truly shines in component-based frameworks like React, Svelte, and Vue. Here's why.
In a component framework, you're already colocating markup, logic, and styles. Tailwind fits naturally because each component defines its own utility classes inline, eliminating the need for separate CSS files per component:
// React - Tailwind colocated with the component
function Card({ title, children }) {
return (
<div className="border rounded-lg p-4 bg-card hover:shadow-sm transition">
<h3 className="text-lg font-semibold">{title}</h3>
<p className="text-sm text-muted-foreground mt-2">{children}</p>
</div>
);
}
<!-- Svelte - the class list travels with the component -->
<script>
export let title;
</script>
<div class="border rounded-lg p-4 bg-card">
<h3 class="text-lg font-semibold">{title}</h3>
<slot />
</div>
<!-- Vue - scoped styles without scoping overhead -->
<template>
<div class="border rounded-lg p-4 bg-card">
<h3 class="text-lg font-semibold">{{ title }}</h3>
<slot />
</div>
</template>
With vanilla CSS in a component world, you have to invent class names for every variant - .card, .card--large, .card--featured, .card__title, .card__body. Then manage CSS imports, watch for name collisions, and deal with specificity when overriding styles. Tailwind eliminates all of that.
No naming, no imports, no collisions. Each component is self-contained. You can move, rename, or delete a component without hunting through CSS files for orphaned styles.
Framework integrations are excellent:
- React/Next.js - Tailwind is the recommended styling approach in the Next.js docs
- Svelte/SvelteKit - Works with Svelte's scoped styles, no conflicts
- Vue/Nuxt - First-class support via
@nuxtjs/tailwindcss - Solid, Qwik, Astro - All have official or community Tailwind integrations
A Note on Raw HTML
I have to be honest: Tailwind is less enjoyable in a raw HTML codebase like this blog. When you're writing the same class="border rounded-lg p-4 bg-card text-sm text-muted-foreground" string for the fiftieth time across separate template files, you start to feel the repetition. There's no component to encapsulate it, no props to vary - just copy-pasting long class strings.
In a Go html/template context, I can't define a reusable Card component like I would in React. I have to repeat the utility classes in every template that needs a card. When I want to tweak the card design, I need to find and update every instance across multiple files.
Is it still better than writing vanilla CSS? Yes - for me, the consistency and design tokens are worth it. But the friction is real, and I wouldn't recommend Tailwind for a site built entirely with raw HTML includes or server-side templates unless you have a way to abstract repeated patterns (partials, macros, etc.). If your stack doesn't support component abstractions, a traditional CSS approach with well-named classes might serve you better.
For component frameworks (React, Svelte, Vue), this problem disappears - each component is written once, colocated, and reused everywhere.
What I Don't Love
No tool is perfect. A few things bother me:
- Long class strings - A complex element can have 10+ classes in its HTML. It's readable but ugly
- Repetition in raw HTML - As mentioned above, without components, you repeat yourself a lot
- Learning curve - You need to learn the utility names.
items-centerisalign-items: center.justify-betweenisjustify-content: space-between. It becomes intuitive but takes time - Not for everyone - If you prefer writing semantic class names and styling in CSS files, Tailwind will feel wrong. That's okay
What Not to Use
Before Tailwind, I considered other approaches:
| Approach | Why I didn't choose it |
|---|---|
| Vanilla CSS | Lots of repetition, naming fatigue, specificity management |
| Bootstrap | Heavy, opinionated components, hard to customize, everyone's site looks the same |
| CSS-in-JS | Requires a JavaScript framework (React, Vue), adds runtime overhead |
| SCSS / Sass | Preprocessor-only, no design system enforcement, still need to name things |
| Open Props | Interesting design tokens but no utility class system, less ergonomic |
Tailwind sits in the sweet spot: utility classes for rapid development, design tokens for consistency, zero runtime, works with any backend.
The Bottom Line
Tailwind CSS changed how I think about styling. I don't write CSS the old way anymore - I compose it. The result is a site that's consistent, responsive, and easy to maintain, with a small CSS footprint and zero JavaScript overhead.
This blog uses Tailwind v4 with custom Catppuccin-inspired theme variables, the typography plugin for Markdown content, and a dark mode toggle that swaps all colors in a single line of JavaScript. The entire design system lives in one input.css file.
If you haven't tried Tailwind since its early days, give v4 a look. The CSS-first configuration and improved performance make it the best version yet.