Leroy
Web Development

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 - sets display: flex
  • items-center - aligns items vertically
  • gap-4 - adds gap: 1rem
  • border - adds a 1px solid border
  • rounded-lg - rounds the corners
  • p-4 - adds padding: 1rem
  • bg-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-center is align-items: center. justify-between is justify-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.

Related Posts