A quick note: I created this setup a several months ago so I may have missed some details in this post. There was a bit of discussion on the Filament repo about this. If you spot anything or have any questions, please let me know.
The FilamentThemes guide shows how to run Tailwind v4 alongside Filament's v3 requirements using npm aliasing. This works, but for production apps, pnpm workspaces provide better isolation and fewer conflicts.
The Problem
Filament needs Tailwind v3, but you want Tailwind v4 features on your frontend. Package aliasing works but can be fragile.
Why pnpm Workspaces?
After trying npm workspaces → yarn workspaces → pnpm workspaces, we found pnpm's isolatedModules
feature provides true dependency separation and enables concurrent development without conflicts.
Setup Guide
Project Structure
project-root/
├── packages/
│ ├── frontend/ # Tailwind v4 environment
│ │ ├── package.json
│ │ └── postcss.config.js
│ └── admin/ # Tailwind v3 environment
│ ├── package.json
│ └── postcss.config.js
├── pnpm-workspace.yaml
├── vite.frontend.config.js
├── vite.admin.config.js
└── package.json
Setting Up Workspaces
First, configure the workspace structure:
# pnpm-workspace.yaml
packages:
- "packages/*"
// package.json (root)
{
"private": true,
"type": "module",
"workspaces": ["packages/*"],
"pnpm": {
"isolatedModules": true
},
"scripts": {
"dev": "concurrently \"pnpm run watch:frontend\" \"pnpm run watch:admin\"",
"build": "concurrently \"pnpm run build:frontend\" \"pnpm run build:admin\"",
"watch:frontend": "vite build --watch --config vite.frontend.config.js",
"watch:admin": "vite build --watch --config vite.admin.config.js"
}
}
Frontend Package (Tailwind v4)
// packages/frontend/package.json
{
"name": "frontend",
"private": true,
"type": "module",
"dependencies": {
"@tailwindcss/postcss": "^4.0",
"tailwindcss": "^4.0"
// Add any additional PostCSS plugins or Tailwind plugins as needed
}
}
// packages/frontend/postcss.config.js
export default {
plugins: {
"@tailwindcss/postcss": {},
// Add other PostCSS plugins as needed
},
};
Admin Package (Tailwind v3)
// packages/admin/package.json
{
"name": "admin",
"private": true,
"type": "module",
"dependencies": {
"tailwindcss": "^3.4.3",
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.12",
"autoprefixer": "^10.4.19",
"postcss": "^8.4.38",
"postcss-nesting": "^12.1.2"
}
}
// packages/admin/postcss.config.js
import tailwindcss from "tailwindcss";
import tailwindcssNesting from "tailwindcss/nesting/index.js";
import autoprefixer from "autoprefixer";
export default {
plugins: [tailwindcss(), tailwindcssNesting(), autoprefixer],
};
Vite Configuration
Configure separate Vite builds that point to their respective workspace packages:
// vite.frontend.config.js
import { defineConfig } from "vite";
import laravel from "laravel-vite-plugin";
import path from "path";
const postcssPath = path.resolve("packages/frontend/postcss.config.js");
const tailwindPath = path.resolve("packages/frontend/node_modules/tailwindcss");
export default defineConfig({
css: {
postcss: postcssPath,
},
resolve: {
alias: {
tailwindcss: tailwindPath,
},
},
plugins: [
laravel({
input: ["resources/css/app.css", "resources/js/app.js"],
buildDirectory: "frontend",
refresh: true,
}),
],
});
// vite.admin.config.js
import { defineConfig } from "vite";
import laravel from "laravel-vite-plugin";
import path from "path";
const postcssPath = path.resolve("packages/admin/postcss.config.js");
const tailwindPath = path.resolve("packages/admin/node_modules/tailwindcss");
export default defineConfig({
css: {
postcss: postcssPath,
},
resolve: {
alias: {
tailwindcss: tailwindPath,
},
},
plugins: [
laravel({
input: ["resources/css/filament/admin/theme.css"],
buildDirectory: "admin",
refresh: true,
}),
],
});
CSS Files
/* resources/css/app.css - Frontend using Tailwind v4 */
@import "tailwindcss";
@layer base {
html {
scroll-behavior: smooth;
color-scheme: dark light;
}
}
/* resources/css/filament/admin/theme.css - Admin using Tailwind v3 */
@import "/vendor/filament/filament/resources/css/theme.css";
@config 'tailwind.config.js';
@tailwind base;
@tailwind components;
@tailwind utilities;
Filament Configuration
Don't forget to update your Filament panel provider to use the correct build directory:
// app/Providers/Filament/AdminPanelProvider.php
->viteTheme('resources/css/filament/admin/theme.css', 'admin')
The second parameter must match the buildDirectory
in your vite.admin.config.js
:
// vite.admin.config.js
laravel({
input: ["resources/css/filament/admin/theme.css"],
buildDirectory: "admin", // This must match!
refresh: true,
});
Running Both Environments
Both environments can run simultaneously:
pnpm run dev # Runs both frontend and admin builds concurrently
Frontend runs on port 3000 (Tailwind v4), admin on port 3001 (Tailwind v3).
Benefits
- True isolation: Each environment has its own
node_modules
- no version conflicts possible - Easier debugging: When something breaks, you know exactly which environment is affected
- Independent upgrades: Update Tailwind v4 frontend without breaking Filament admin
- Concurrent development: Both builds run simultaneously without interference
Trade-offs
Pros: Complete isolation, easier maintenance, concurrent workflows
Cons: Slightly more setup, larger node_modules
Conclusion
While npm aliasing works for quick setups, pnpm workspaces provide a more robust foundation for production applications. The key insight: explicit separation is better than clever aliasing for applications that need to last.