Other Frameworks
Install Tailwind v4 in Laravel, SvelteKit, Astro, Rails, Angular and template-only stacks like Django, Flask and Hugo — plus the recipe that works anywhere.
Tailwind v4 has no framework adapters. Every integration is one of three things: a Vite plugin, a PostCSS plugin, or the standalone CLI. Once you know which one your stack needs, the rest of the setup is the same everywhere.
Which install method#
| Framework | Method | Package | Config file |
|---|---|---|---|
| Laravel | Vite plugin | @tailwindcss/vite | vite.config.ts |
| SvelteKit | Vite plugin | @tailwindcss/vite | vite.config.ts |
| Astro | Vite plugin | @tailwindcss/vite | astro.config.mjs |
| Nuxt | Vite plugin | @tailwindcss/vite | nuxt.config.ts |
| SolidStart, Remix, React Router | Vite plugin | @tailwindcss/vite | vite.config.ts |
| Next.js | PostCSS | @tailwindcss/postcss | postcss.config.mjs |
| Angular | PostCSS | @tailwindcss/postcss | .postcssrc.json |
| Ruby on Rails | Gem or CLI | tailwindcss-rails or @tailwindcss/cli | none |
| Django, Flask, Hugo | CLI | @tailwindcss/cli | none |
| Plain HTML | CLI | @tailwindcss/cli | none |
Nuxt and Next.js have their own pages — Vue & Nuxt and React & Next.js.
Any framework: the general recipe#
If your framework is not listed anywhere, these three steps are the whole integration.
- Install the package that matches your build tool. Vite project →
@tailwindcss/vite. PostCSS pipeline →@tailwindcss/postcss. No bundler →@tailwindcss/cli. - Create one CSS file containing
@import "tailwindcss";and put your@themeand custom utilities in it. - Make sure that stylesheet is served, and that Tailwind can see your templates. Source detection starts from the CSS file's directory and walks up to the project root, respecting
.gitignore. Add@sourceonly for files it cannot reach.
There is no tailwind.config.js, no content array and no init step.
Laravel#
Laravel ships with Vite, so use the Vite plugin. Add it to the existing plugins array — the Laravel plugin stays where it is.
npm install tailwindcss @tailwindcss/viteimport { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [
tailwindcss(),
// …
],
})@import "tailwindcss";
@source "../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php";
@source "../../storage/framework/views/*.php";
@source "../**/*.blade.php";
@source "../**/*.js";
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
@vite('resources/css/app.css')
</head>
<body>
<h1 class="text-3xl font-bold underline">
Hello world!
</h1>
</body>
</html>npm run devWhy Laravel needs @source lines#
Source detection starts at the stylesheet's directory — resources/css/ — and walks up to the project root, but it skips anything in .gitignore. Laravel gitignores /vendor and /storage, and those directories contain classes you ship:
vendor/laravel/.../Pagination/— the framework's own paginator views are styled with Tailwind. Without this line, pagination links render unstyled.storage/framework/views/— compiled Blade. Cheap insurance for classes assembled by directives.- Your own
resources/tree — the last two lines name your Blade templates and JS explicitly, so the paths above do not narrow detection tovendor/andstorage/alone.
Add another @source for any package that ships Tailwind-styled Blade views into vendor/.
SvelteKit#
npm install tailwindcss @tailwindcss/viteimport { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
plugins: [
tailwindcss(),
sveltekit(),
],
});@import "tailwindcss";Import it from the root layout so every route inherits it:
<script>
let { children } = $props();
import "../app.css";
</script>
{@render children()}Templates live in src/routes/ and src/lib/, both under the project root, so they are scanned automatically.
@apply in a Svelte <style> block#
A <style> block in a .svelte file compiles as its own stylesheet, with no access to your theme — the same constraint as a Vue SFC. Add @reference to bring the theme into scope:
<h1 class="text-3xl font-bold underline">
Hello world!
</h1>
<style lang="postcss">
@reference "tailwindcss";
:global(html) {
background-color: theme(--color-gray-100);
}
</style>Reference your own src/app.css instead of the package when your theme is customised. @reference reads the theme without emitting anything, so nothing is duplicated in the output — the mechanics are covered in Vue & Nuxt.
Astro#
Astro is Vite-based, and exposes Vite's config under a vite key.
npm install tailwindcss @tailwindcss/vite// @ts-check
import { defineConfig } from "astro/config";
import tailwindcss from "@tailwindcss/vite";
// https://astro.build/config
export default defineConfig({
vite: {
plugins: [tailwindcss()],
},
});@import "tailwindcss";Import the stylesheet from the component frontmatter:
---
import "../styles/global.css";
---
<h1 class="text-3xl font-bold underline">
Hello world!
</h1>In a real site, put that import in a shared layout under src/layouts/ rather than in every page. Templates live in src/pages/, src/layouts/ and src/components/ — all scanned automatically, including any .jsx, .vue or .svelte island components alongside them.
Ruby on Rails#
Two options. The gem is the official guide's answer and manages the build for you; the CLI is the escape hatch when you want the same setup as every other non-bundled project.
With tailwindcss-rails#
bundle add tailwindcss-rails
./bin/rails tailwindcss:install./bin/devThe installer creates the input stylesheet, wires the layout to the compiled output, and adds a watch process to Procfile.dev — which is what ./bin/dev runs.
| Path | Role |
|---|---|
app/assets/tailwind/application.css | Your input file — @import "tailwindcss";, @theme, custom utilities |
app/assets/builds/tailwind.css | Generated output, served by the asset pipeline |
Two rake tasks drive it directly if you are not using ./bin/dev:
./bin/rails tailwindcss:build # one-off, for deploys
./bin/rails tailwindcss:watch # rebuild on changeTemplates live in app/views/, plus app/helpers/ and app/components/ if you use ViewComponent — all under the project root and picked up automatically.
With @tailwindcss/cli#
If you would rather not add a gem, the CLI is identical to any other Node-free-ish setup:
npm install tailwindcss @tailwindcss/cli@import "tailwindcss";npx @tailwindcss/cli -i ./app/assets/stylesheets/application.css -o ./app/assets/builds/tailwind.css --watchAdd the watch command to Procfile.dev alongside rails server, and use --minify instead of --watch in your deploy task.
Django, Flask and Hugo#
None of these have a JavaScript build step, so run the CLI beside the dev server. The pattern is the same in all three — only the paths change.
npm install tailwindcss @tailwindcss/cli@import "tailwindcss";{
"scripts": {
"dev": "npx @tailwindcss/cli -i ./src/input.css -o ./static/css/site.css --watch",
"build": "npx @tailwindcss/cli -i ./src/input.css -o ./static/css/site.css --minify"
}
}Run npm run dev in one terminal and your framework's server in another.
Django#
Templates live in templates/ at the project root and in each app's <app>/templates/ directory. Both are under the project root, so source detection finds them with no configuration. Output goes into a static/ directory listed in STATICFILES_DIRS:
{% load static %}
<link rel="stylesheet" href="{% static 'css/site.css' %}">If your CSS input file lives inside an app directory, @source the project-level template folder explicitly:
@source "../../templates";Flask#
Templates are in templates/, static assets in static/:
<link rel="stylesheet" href="{{ url_for('static', filename='css/site.css') }}">Hugo#
Layouts live in layouts/, content in content/. Write the compiled CSS into static/css/ so Hugo copies it verbatim:
<link rel="stylesheet" href="{{ "css/site.css" | relURL }}">Plain HTML#
No framework, no bundler, no config file.
npm install tailwindcss @tailwindcss/cli
npx @tailwindcss/cli -i ./src/input.css -o ./dist/output.css --watch<link rel="stylesheet" href="/dist/output.css">
<h1 class="text-3xl font-bold underline">Hello world!</h1>Angular#
Angular's build runs PostCSS, so it uses @tailwindcss/postcss — with a .postcssrc.json rather than the postcss.config.mjs other PostCSS setups use.
npm install tailwindcss @tailwindcss/postcss postcss --force{
"plugins": {
"@tailwindcss/postcss": {}
}
}@import "tailwindcss";ng servesrc/styles.css is already listed in the styles array in angular.json, so no wiring is needed. Templates live in src/app/ as .html files or inline template strings — both are scanned, since detection reads files as text.
Two Angular specifics:
--forceis part of the official install command. Angular's dependency tree can otherwise refuse the install over a peer range.[ngClass]and[class]bindings follow the same rule as everywhere else.[ngClass]="'bg-' + colour + '-500'"produces no CSS; map component state to complete class strings instead. The IntelliSense extension already treatsngClassas a class attribute.
Common mistakes#
- Following a guide that opens with an
initcommand. v4 has no config file to generate, so that step was removed. Everything after it in that guide is v3 too. - Using the bare
tailwindcssPostCSS plugin name.plugins: { tailwindcss: {} }throws in v4; it must be@tailwindcss/postcss. - Skipping Laravel's
@sourcelines.vendor/andstorage/are gitignored, so paginator views and compiled Blade are invisible to source detection and render unstyled. - Keeping a v3-era framework integration.
@astrojs/tailwindand the v3 Nuxt module inject@tailwinddirectives and atailwind.config.js. Remove them before installing the v4 plugin. - Installing
@tailwindcss/vitein a non-Vite project. Angular, Rails and Django have no Vite pipeline; the plugin is loaded and silently never runs. - Pointing the CLI's
-oat a path the server does not serve. The build succeeds and writes a perfectly good file that nothing ever requests. - Forgetting
--minifyin the production script. The--watchcommand is for development; deploys need the minified build. - Building class names in template syntax. Blade's
{{ }}, Django's{% %}, Hugo's{{ }}and Angular's[ngClass]are all evaluated long after Tailwind has finished scanning.
Related topics#
- Installation — every install method side by side
- React & Next.js — the PostCSS path in detail
- Vue & Nuxt —
@referencein component style blocks - Project Setup — source detection,
@sourceand safelisting - Production Builds — minification and bundle size