This episode covers Tailwind integration in modern frameworks: Next.js App Router, Vite, SvelteKit, Astro, and Remix, with the patterns and caveats of each. You also learn compatibility with CSS-in-JS using twin.macro and hybrid approaches.

Every framework has its own way of handling assets and CSS. Episode 18 covers Tailwind integration in five popular frameworks: Next.js, Vite, SvelteKit, Astro, and Remix — complete with patterns, content paths, and their caveats. You'll also see coexistence with CSS-in-JS.
The core of every integration is the same: Tailwind produces static CSS, and the only difference is where the input file is imported and which files go into content. Once you understand this pattern, moving between frameworks stops being scary.
Install Tailwind with PostCSS, then initialize the config:
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -pSet up content to cover the App Router:
module.exports = {
content: ["./app/**/*.{js,ts,jsx,tsx,mdx}", "./components/**/*.{js,ts,jsx,tsx}"],
theme: { extend: {} },
plugins: [],
};Then import the global CSS in the root layout:
import "./globals.css";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="id">
<body>{children}</body>
</html>
);
}The pattern content: ["./app/**/*.{js,ts,jsx,tsx,mdx}"] must include .mdx files if you use MDX. Import CSS only in the root layout — not in components — to avoid duplication.
Vite provides an official plugin:
npm install tailwindcss @tailwindcss/viteimport tailwindcss from "@tailwindcss/vite";
export default {
plugins: [tailwindcss()],
};This plugin uses Tailwind 4's CSS-first syntax — no longer needing tailwind.config.js for basic cases.
SvelteKit uses PostCSS. Install, then set the config with content covering .svelte:
module.exports = {
content: ["./src/**/*.{html,js,svelte,ts}"],
theme: { extend: {} },
plugins: [],
};Don't forget to add .svelte to the content glob — the most common mistake in Svelte projects.
Astro also uses PostCSS. Content must cover .astro and .md when used:
module.exports = {
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,ts}"],
theme: { extend: {} },
plugins: [],
};Remix is identical to Next.js: PostCSS, content covering app/**/*.{js,ts,jsx,tsx}, and a global CSS import in the root route.
Info
The most common pattern across every setup: check the framework's file extension list in the content glob. Next.js needs mdx, SvelteKit needs svelte, Astro needs astro and md. One missing extension means classes in those files are never generated.
Tailwind and CSS-in-JS can coexist. A common hybrid approach is twin.macro — using Tailwind utilities inside CSS-in-JS files as a build-time macro:
import tw from "twin.macro";
const Button = tw.button`
rounded bg-blue-500 px-4 py-2 text-white
hover:bg-blue-600
`;tw.button is resolved at build time into plain CSS — no extra runtime from Tailwind. This approach gives you CSS-in-JS ergonomics (scoped, colocated) plus static CSS performance.
An increasingly common alternative: use plain Tailwind in markup and keep CSS-in-JS only for cases that genuinely need runtime logic like dynamic theming. Episode 6 already covered dynamic theming with CSS variables — often that's enough, without any CSS-in-JS at all.
Episode 18 mapped Tailwind integration in modern frameworks: Next.js App Router, Vite, SvelteKit, Astro, Remix, plus coexistence with CSS-in-JS using twin.macro or a lighter hybrid approach.
Key takeaways:
.mdx.@tailwindcss/vite plugin..svelte in the content glob; Astro needs .astro and .md.Next, in episode 19, we'll cover component libraries & headless UI patterns — building accessible components with Headless UI, Radix, and Tailwind UI, and documenting components with Storybook.