-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Investigating Inconsistent Config Loading with @tailwindcss/vite #18469
-
Hello Tailwind Team,
First of all, thank you for all the incredible work on v4. The new engine and the dedicated Vite plugin are exciting developments.
I was recently setting up a new project to try out @tailwindcss/vite
as an all-in-one replacement for the older PostCSS setup. My mental model, based on past experience, was that the plugin would automatically find my tailwind.config.js
at the project root and use it to configure everything. During this process, I ran into some interesting behavior that I wanted to share.
To understand it better, I created a minimal reproduction repository, which can be found here:
➡️ https://github.com/philippedev101/tailwind-vite-bug-repro
To trace the issue, I also cloned the tailwindcss
and vite
repositories locally and used pnpm overrides
to point the MRE to my local builds. I then instrumented several files with extensive logging. (As a side note, I did have to patch the exports
map in tailwindcss/package.json
to get the local package to import correctly in my project, changing the "import"
field from src/index.ts
to dist/lib.mjs
.)
The minimal repo uses a standard setup, but with one key feature: the main CSS file is in a subdirectory (src/client/main.css
). The pnpm overrides
can be removed and the issue still persists with the official npm packages.
The Core Observation
When I run the MRE, I see very specific and seemingly contradictory behavior:
- Classes from the
safelist
in mytailwind.config.js
(like.rotate-12
) are correctly generated in the final CSS. - Utility classes used in my
content
files (like.text-blue-500
inindex.html
) are not detected and are missing from the final CSS.
This suggests that the config file is being found and read, but that the content
property is being handled differently or ignored by the content scanning pipeline.
The Debugging Trace
My logging within the local builds revealed a very specific chain of events that seems to cause this:
-
Incorrect
base
path calculation in the Vite Plugin.
Inpackages/@tailwindcss-vite/src/index.ts
, theRoot.generate()
method calculates a base path from the directory of the CSS file being processed, not the Vite project root.- It correctly has
this.base = "/path/to/repro"
. - But it calculates and uses
inputBase = "/path/to/repro/src/client"
. - It then calls the core
compile
function with this incorrectinputBase
.
- It correctly has
-
The
compat
Layer Receives the Wrong Path.
The core engine'sparseCss
function (inpackages/tailwindcss/src/index.ts
) receives this incorrectbase
path and passes it down toapplyCompatibilityHooks
inpackages/tailwindcss/src/compat/apply-compat-hooks.ts
. -
Config Resolution Fails to Process User
content
.
TheapplyCompatibilityHooks
function is responsible for finding and merging configs.- Because our CSS doesn't use an explicit
@config
directive, the function enters a path to find an implicittailwind.config.js
. - However, its call to
upgradeToFullPluginSupport
and the subsequent calls toresolveConfig
(inpackages/tailwindcss/src/compat/config/resolve-config.ts
) end up processing only the built-in default theme config. - Crucially, the user's
tailwind.config.js
is never added to the list offiles
for theresolveConfig
function to process for itscontent
key.
- Because our CSS doesn't use an explicit
-
An Empty
content
Array is Returned.
Because the user's config was never processed for itscontent
key in this pipeline, theresolveConfig
function returns a final, merged config wherecontent.files
is an empty array. -
The
vite
Plugin's Fallback is Triggered.
The@tailwindcss/vite
plugin receives this resolved config with no content sources. It then activates its own fallback logic, setting the scanner to watch/*
in the project root. This is a very broad pattern that doesn't respect the specific, more efficient globs defined in our actualtailwind.config.js
.
Summary of Findings:
It appears there are two separate config-loading mechanisms at play. One process seems to correctly find tailwind.config.js
and process the safelist
, but a second, more complex process, initiated by the @tailwindcss/vite
plugin, fails. This second process fails because the plugin passes an incorrect base path to the core engine's compatibility layer, which then doesn't correctly load the content
property from the user's config file, causing the JIT scanner to not work as intended.
A Few Questions
This deep dive left me with a few questions about the intended design, which would be super helpful to understand:
- Is it the intended behavior for the
@tailwindcss/vite
plugin to use the CSS file's directory as thebase
for config resolution, instead of the Vite project root? - The
compat
layer'sresolveConfig
doesn't seem to perform an upward search for a config file. Is that the expected logic for that part of the system? - Most importantly, what is the recommended way to set up a project like the MRE? It would be incredibly helpful to see a reference example of
@tailwindcss/vite
being used without a PostCSS pipeline where the CSS entry point is in a subdirectory.
Thank you again for your time and for looking into this. I'm really excited about where the project is headed!
Environment
- OS: Ubuntu 24.04.2 LTS
- Node.js: v22.17.0
- pnpm: 10.12.4
- Rust: rustc 1.88.0 (6b00bc388 2025年06月23日)
- Shell: zsh 5.9
- Tailwind CSS Commit:
b24457a9f4101f20a3c3ab8df39debe87564fe8a
- Vite Commit:
2e8050e4cd8835673baf07375b7db35128144222
Beta Was this translation helpful? Give feedback.
All reactions
What is happening is that you've configured Tailwind v4 using v3 methods. In v4, the tailwind.config.js
is phased out and is not the main container for configuration any more.
V4 has automatic source scanning. Thus, it finds rotate-12
as a string inside tailwind.config.js
, not that it loads the safelist
as configuration. So, it adds rotate-12
to the CSS output.
So then why does Tailwind not output the classes in index.html
, text-3xl
, font-bold
and text-blue-500
? This is because all of these classes rely on named Tailwind theme tokens for --font-size
, --font-weight
and --color
. However, these do not exist in Tailwind compilation runs in your reproduction. This is because you haven't includ...
Replies: 2 comments
-
What is happening is that you've configured Tailwind v4 using v3 methods. In v4, the tailwind.config.js
is phased out and is not the main container for configuration any more.
V4 has automatic source scanning. Thus, it finds rotate-12
as a string inside tailwind.config.js
, not that it loads the safelist
as configuration. So, it adds rotate-12
to the CSS output.
So then why does Tailwind not output the classes in index.html
, text-3xl
, font-bold
and text-blue-500
? This is because all of these classes rely on named Tailwind theme tokens for --font-size
, --font-weight
and --color
. However, these do not exist in Tailwind compilation runs in your reproduction. This is because you haven't included these in your input CSS.
If we followed the vite integration instructions and replaced your CSS file with:
@import "tailwindcss";
This is shorthand for:
@layer theme, base, components, utilities; @import "tailwindcss/theme.css" layer(theme); @import "tailwindcss/preflight.css" layer(base); @import "tailwindcss/utilities.css" layer(utilities);
Which imports the default theme tokens as mentioned previously.
Thus, having your src/index.css
be:
@import "tailwindcss";
Should now see all the expected class names in the output CSS file.
Beta Was this translation helpful? Give feedback.
All reactions
-
❤️ 1
-
Thanks @wongjn extremely helpful!
Beta Was this translation helpful? Give feedback.