Tailwind CSS
Tailwind CSS lets you style components by composing small utility classes directly in your markup, which pairs naturally with Astro’s component-first model. Because Astro ships zero JavaScript by default, Tailwind’s purely build-time, CSS-only output is an ideal match — the styling adds nothing to the client bundle. As of Tailwind v4, the integration runs through a first-class Vite plugin, so there is no PostCSS config and almost no setup. This page covers wiring up the plugin, applying utilities across .astro and framework components, and keeping the workflow fast.
Installing the Vite plugin
Install Tailwind and its official Vite plugin, then create a single CSS entry point that imports Tailwind. In v4, configuration lives in CSS rather than a JavaScript config file, so a one-line @import is all you need to start.
npm install tailwindcss @tailwindcss/vite
Create a global stylesheet that pulls in the framework:
/* src/styles/global.css */
@import "tailwindcss";
Register the plugin with Vite inside your Astro config. Astro exposes the vite key precisely so integrations like this can hook into the underlying build.
// astro.config.mjs
import { defineConfig } from "astro/config";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
vite: {
plugins: [tailwindcss()],
},
});
Finally, import the stylesheet once in a shared layout so every page that uses the layout gets the generated utilities.
---
// src/layouts/BaseLayout.astro
import "../styles/global.css";
const { title } = Astro.props;
---
<html lang="en">
<head>
<meta charset="utf-8" />
<title>{title}</title>
</head>
<body class="bg-slate-50 text-slate-900 antialiased">
<slot />
</body>
</html>
Importing
global.cssin one layout is enough — Vite bundles, dedupes, and tree-shakes the CSS. Re-importing it in every component is harmless but unnecessary.
Applying utilities in components
With the plugin active, write utility classes on any element. Tailwind scans your source files for class names at build time and emits only the CSS you actually use, so the output stays tiny regardless of how many utilities the framework offers.
---
// src/components/Card.astro
interface Props {
title: string;
href: string;
}
const { title, href } = Astro.props;
---
<a
href={href}
class="block rounded-xl border border-slate-200 bg-white p-6 shadow-sm transition hover:border-slate-300 hover:shadow-md"
>
<h3 class="text-lg font-semibold tracking-tight">{title}</h3>
<p class="mt-2 text-sm text-slate-500">Read the guide →</p>
</a>
Astro’s class:list directive composes well with Tailwind when you need conditional classes — pass strings, arrays, or objects and Astro flattens them into a single class attribute:
---
const { active = false } = Astro.props;
---
<button
class:list={[
"rounded-md px-4 py-2 text-sm font-medium",
{ "bg-blue-600 text-white": active, "bg-slate-100 text-slate-700": !active },
]}
>
<slot />
</button>
The same classes work inside hydrated framework islands. Because Tailwind output is global CSS, a React or Svelte component using client:load is styled by the same utility layer with no extra configuration:
---
import Counter from "../components/Counter.tsx";
---
<Counter client:load />
// src/components/Counter.tsx
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button
onClick={() => setCount((c) => c + 1)}
class="rounded bg-emerald-600 px-3 py-1 font-semibold text-white"
>
Count: {count}
</button>
);
}
Customizing the theme
Tailwind v4 reads design tokens from the @theme directive in your CSS. Define custom colors, fonts, or spacing there and they become available as utilities and as CSS variables automatically.
/* src/styles/global.css */
@import "tailwindcss";
@theme {
--color-brand: #4f46e5;
--font-display: "Inter", sans-serif;
}
You can now use bg-brand, text-brand, or font-display anywhere. The tokens are also exposed as var(--color-brand), which lets Astro <style> blocks and scoped styles share the same palette.
| Concern | v4 (Vite plugin) | v3 (legacy) |
|---|---|---|
| Setup | @tailwindcss/vite plugin | @astrojs/tailwind + PostCSS |
| Config | CSS @theme directive | tailwind.config.js |
| Content paths | Auto-detected | Declared in content array |
| Import | @import "tailwindcss" | @tailwind directives |
The older
@astrojs/tailwindintegration targets Tailwind v3 and is now deprecated. For new projects use@tailwindcss/vitewith Tailwind v4 as shown above.
Best practices
- Import your single
global.cssonce in a base layout rather than per component to avoid duplicate@importwork. - Define brand colors, fonts, and spacing in
@themeso utilities and scoped<style>blocks share one source of truth. - Prefer composing utilities directly in markup; reach for
@applyonly to dedupe genuinely repeated class strings. - Keep utility classes in Astro’s
class:listdirective for conditional logic instead of string concatenation. - Let Tailwind purge unused styles automatically — never hand-build long, dynamically concatenated class names it cannot detect.
- Co-locate islands and static components; the shared global stylesheet styles both with zero extra setup.