Tailwind v4 rethinks how you configure the framework. If you’re used to tailwind.config.js, the new CSS-first approach takes some adjustment — but once it clicks, it’s cleaner. This guide walks through a complete Tailwind v4 setup in an Astro 5.x project, including dark mode and the Typography plugin.
Prerequisites: Node.js 18+, an Astro 5.x project (npm create astro@latest), basic familiarity with Tailwind.
Step 1: Install Dependencies
Install the Tailwind v4 Vite plugin and the Typography plugin:
npm install tailwindcss @tailwindcss/vite @tailwindcss/typographyStep 2: Configure the Vite Plugin
Open astro.config.mjs and add the Tailwind Vite plugin:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import tailwindcss from '@tailwindcss/vite';
import mdx from '@astrojs/mdx';
export default defineConfig({
integrations: [mdx()],
vite: {
plugins: [tailwindcss()],
},
});The key detail: tailwindcss() goes in vite.plugins, not in integrations. The Vite plugin is how Tailwind v4 hooks into the build pipeline. There is no @astrojs/tailwind integration in v4 — the old integration is a v3 pattern.
Step 3: Create Your CSS
Create src/styles/global.css (or wherever your main stylesheet lives):
/* Import the Tailwind base */
@import "tailwindcss";
/* Enable the Typography plugin */
@plugin "@tailwindcss/typography";
/* Define your design tokens */
@theme {
--color-accent: oklch(0.75 0.18 175);
--color-accent-hover: oklch(0.68 0.16 175);
--font-family-heading: "JetBrains Mono", monospace;
--font-family-body: "Inter", sans-serif;
}
/* Dark mode variant */
@custom-variant dark (&:where(.dark, .dark *));Import this stylesheet from your base layout:
---
// src/layouts/BaseLayout.astro
import '../styles/global.css';
---Step 4: Set Up Dark Mode
The @custom-variant dark line in your CSS defines when dark mode applies. The simplest approach: toggle a dark class on <html> and persist the preference in localStorage.
Add this script to your <head> — it must run before the page renders to avoid a flash of the wrong theme:
// Inline script in BaseLayout.astro (use is:inline to skip processing)
(function () {
const stored = localStorage.getItem('theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const isDark = stored === 'dark' || (!stored && prefersDark);
document.documentElement.classList.toggle('dark', isDark);
})();Then wire up a toggle button to flip the class and update localStorage:
document.getElementById('theme-toggle').addEventListener('click', () => {
const isDark = document.documentElement.classList.toggle('dark');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
});Step 5: Add Typography
The Typography plugin is already enabled via @plugin "@tailwindcss/typography" in your CSS. To apply prose styling to post content, add the prose class to your content wrapper:
---
// src/layouts/PostLayout.astro
const { post } = Astro.props;
---
<article class="prose prose-lg max-w-none">
<slot />
</article>Note the absence of dark:prose-invert. If you’re setting custom --tw-prose-* variables in @theme for both light and dark modes, adding dark:prose-invert will override your custom values with Typography’s defaults in dark mode. Only use dark:prose-invert if you’re relying entirely on the Typography plugin’s built-in dark palette.
You can customize the prose styling with @theme variables. For example, to change the link color:
@theme {
--tw-prose-links: oklch(0.75 0.18 175);
--tw-prose-invert-links: oklch(0.75 0.18 175);
}Common Pitfalls
After setting this up on a few projects, these are the mistakes I’ve seen (and made):
Wrap-Up
Tailwind v4 in Astro is a clean setup once you understand where each piece goes: Vite plugin in astro.config.mjs, configuration in your CSS file, dark mode via a class variant and a small inline script. The main adjustment is moving config out of JavaScript and into CSS — after which the whole thing feels simpler and more composable.
If you run into issues, the most common culprits are the wrong integration name (@astrojs/tailwind vs @tailwindcss/vite) and missing @reference in scoped style blocks.