If you have spent any time reading about front-end development in the past few years, you have almost certainly come across Tailwind CSS. It has a reputation for being polarising — some developers love writing flex gap-4 items-center directly in their HTML; others find it looks like noise. But underneath the debate is a genuinely practical tool that can make styling faster once you understand what it is actually doing.
This guide covers the essentials: the utility-class idea, how to get Tailwind running, responsive design, and the newer CSS-first configuration in Tailwind v4. You will write real code throughout.
What Tailwind CSS actually is
Tailwind is a utility-first CSS framework. Instead of giving you pre-built components like a navbar or a card, it gives you hundreds of small, single-purpose classes — one class per CSS property. You compose them directly on your HTML elements.
Compare the two mental models:
- Component-based (Bootstrap, etc.):
class="btn btn-primary"— you apply a named component and accept whatever CSS the framework decided it should have. - Utility-based (Tailwind):
class="bg-blue-600 text-white font-semibold py-2 px-4 rounded"— you describe exactly what you want, class by class.
The upside of the utility approach: you almost never write a new CSS rule. Every design decision stays in the markup, which means you can read a component and immediately know what it looks like. The downside people cite — verbose class strings — is real, but tooling (component extraction, editor completion) keeps it manageable in practice.
Installing Tailwind v4 with Astro
Tailwind v4 moved to a Vite-first architecture. If you are using Astro (which runs on Vite), the setup is a few commands. The older @astrojs/tailwind integration is deprecated for v4; the new path uses @tailwindcss/vite.
npm install tailwindcss @tailwindcss/vite
Then register the plugin in astro.config.mjs. Note that it goes in vite.plugins, not in integrations:
import { defineConfig } from 'astro/config';
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
vite: {
plugins: [tailwindcss()],
},
});
Create a global stylesheet — for example src/styles/global.css — with a single import:
@import "tailwindcss";
Import that file once in your root layout, run npm run dev, and Tailwind is live. No config file required for basic use; v4 auto-detects your source files.
Plain Vite or other frameworks
If you are not on Astro, the same plugin approach applies to any Vite project. For non-Vite toolchains (Webpack, PostCSS), Tailwind still ships a PostCSS plugin — check the official installation docs for your specific setup.
The basics: utility classes
Once Tailwind is installed, you style elements by adding classes directly to your HTML. Here is a simple card component:
<div class="bg-white rounded-xl shadow p-6 max-w-sm">
<h2 class="text-xl font-bold text-gray-900 mb-2">Greenfield Lawn Care</h2>
<p class="text-gray-600 text-sm leading-relaxed">
Professional lawn care for the Denver metro area.
Licensed, insured, and locally owned.
</p>
<a href="/contact"
class="mt-4 inline-block bg-green-600 text-white text-sm font-semibold
py-2 px-4 rounded-lg hover:bg-green-700 transition-colors">
Get a free quote
</a>
</div>
Reading those class names tells you the complete story: white background, extra-large rounded corners, a shadow, 24px of padding, capped at a small maximum width. No digging through a CSS file to find out what .card does.
A few categories you will use constantly:
- Spacing:
p-4(padding 1rem),m-2(margin 0.5rem),gap-6,space-y-4 - Typography:
text-lg,font-bold,leading-relaxed,text-gray-700 - Colors:
bg-blue-600,text-white,border-gray-200 - Layout:
flex,grid,items-center,justify-between,grid-cols-3 - Sizing:
w-full,max-w-2xl,h-12 - Interactivity:
hover:bg-blue-700,focus:outline-none,transition-colors
Responsive design
Tailwind uses a mobile-first breakpoint system. An unprefixed class applies at all screen sizes. Prefixing with a breakpoint name applies the class only at that size and above.
The default breakpoints are sm (640px), md (768px), lg (1024px), xl (1280px), and 2xl (1536px).
<!-- Single column on mobile, three columns on large screens -->
<div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
<div class="bg-white rounded-xl p-6 shadow">Card 1</div>
<div class="bg-white rounded-xl p-6 shadow">Card 2</div>
<div class="bg-white rounded-xl p-6 shadow">Card 3</div>
</div>
You read this left-to-right: grid-cols-1 always applies, lg:grid-cols-3 overrides it on large viewports. No media query blocks to write or maintain.
Customising your design: the v4 @theme directive
Tailwind v4 replaced tailwind.config.js with a CSS-first configuration model. You define your design tokens directly in CSS using @theme, right in the same stylesheet where you import Tailwind:
@import "tailwindcss";
@theme {
--color-brand: oklch(55% 0.22 265);
--color-brand-dark: oklch(45% 0.22 265);
--color-surface: oklch(98% 0.005 265);
--font-sans: 'Inter', sans-serif;
--radius-card: 1rem;
--spacing-section: 5rem;
}
Every variable you declare in @theme automatically becomes a utility class. After the block above, you can write bg-brand, text-brand-dark, rounded-card, and py-section in your HTML — exactly like the built-in classes. No plugin, no rebuild step, no separate config file to keep in sync.
The distinction worth knowing: variables inside @theme generate utility classes; variables on :root are plain CSS custom properties used in arbitrary values. Use @theme when you want Tailwind to generate a class from the token.
Hover, focus, and state variants
Tailwind handles interactive states through prefixes, the same way it handles breakpoints. Stack them freely:
<button class="bg-blue-600 text-white px-5 py-2.5 rounded-lg font-semibold
hover:bg-blue-700
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2
disabled:opacity-50 disabled:cursor-not-allowed
transition-colors duration-150">
Submit
</button>
Common state variants include hover:, focus:, active:, disabled:, checked:, dark: (for dark-mode), first:, last:, and group-hover: (for styling children when a parent is hovered).
When classes get repetitive: @apply
If you find yourself repeating the same long class string across many elements, Tailwind provides @apply to extract them into a regular CSS class:
@layer components {
.btn-primary {
@apply bg-blue-600 text-white font-semibold py-2 px-4 rounded-lg
hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500
transition-colors;
}
}
Use this sparingly. @apply is most useful for things like button variants or form input resets — situations where the exact same styling genuinely belongs on its own named class. Reaching for it everywhere defeats the purpose of the utility-first model; your HTML ends up back to "what does .btn-primary look like?" with no answer visible in the markup.
In component-based frameworks like Astro or React, the better pattern is usually to extract a component (e.g. a <Button /> component) rather than a CSS class. The utility classes live in one place inside the component file, and you compose the component instead.
What gets shipped: only the classes you use
One concern people raise about Tailwind is file size — hundreds of utilities must produce an enormous stylesheet. In practice the opposite is true. Tailwind's build step scans your source files, detects every class string present, and generates a stylesheet containing only those classes. A production build for a typical marketing site is often under 15 KB of CSS.
In v4, this scanning is automatic. You do not need to configure a content array listing which files to scan; Tailwind reads from your Vite dependency graph.
A quick note on editor tooling
Install the Tailwind CSS IntelliSense extension for VS Code (or its equivalent in your editor). It adds autocomplete for class names, hover previews that show the underlying CSS, and warnings when you mistype a class. Working without it is like writing JavaScript without type hints — technically possible, but slower and more error-prone.
Is Tailwind right for every project?
Tailwind shines in situations where you control the markup — your own themes, components, and marketing pages. It is less natural when you are styling third-party HTML you cannot modify, or when a small team is more comfortable with traditional CSS architecture. For content-heavy sites built around a component system, utility classes generally pay off quickly: less context-switching between files, consistent spacing and color scales enforced by the framework, and faster iteration on layout tweaks.
The v4 release removed most of the friction that used to trip people up: the config file is optional, setup is a single Vite plugin, and the @theme directive makes custom tokens feel native rather than bolted on. If you have been curious about Tailwind but put off by the JavaScript config or the perceived learning curve, v4 is a good moment to give it an honest try.
Start with the official docs on utility classes and the responsive design guide — the documentation is thorough and the examples are practical. Most developers find the model clicks after a few hours of real use.