Repository: tailwindlabs/tailwindcss

Problem statement:
Changes in CSS file do not update Next.js setup
<img width="701" alt="Image" src="https://github.com/user-attachments/assets/b7b9486e-7c1d-4412-8caf-b4bca1fe15a5" />

Section says that i can use CSS variables directly. It might be true in case of `static` theme, but it does not work for custom non-static theme which gets only the values picket up by the scanner from classnames. 

The confusion is that it is never mentioned, that scanner does not pickup CSS variables used directly to include them in a server theme layer:

![Image](https://github.com/user-attachments/assets/5884e0ed-9cf9-44a8-9fb5-78fa7e3fbcad)

![Image](https://github.com/user-attachments/assets/2e37afae-b759-43c7-9e2b-9ede48b04b5c)




Hints:
Hey there! This seems like a bug on our end, I agree that this should _just work_. We'll look into it. 

In the mean time, can you maybe share the contents of the file that references the CSS variable? I want to make sure we add a test case for your setup 👍  Also curious what extension you are using? Vite/CLI/PostCSS or something else?
> Hey there! This seems like a bug on our end, I agree that this should _just work_. We'll look into it.
> 
> In the mean time, can you maybe share the contents of the file that references the CSS variable? I want to make sure we add a test case for your setup 👍 Also curious what extension you are using? Vite/CLI/PostCSS or something else?

Hey there, thank you so much for a quick reply.

My setup is:
`"next": 14.2.14` (therefore PostCSS extension)
`"tailwindcss": 4.0.14`
`"@tailwindcss/postcss": 4.0.14`
`"postcss":  8.4.41`

Issue persists both in client and server components

**App server is ran within docker container**


Update:

Seems like containerization is the issue. When i apply the variable and restart the container it gets added to the layer, however when i change it to other variable it is not refreshed until I  restart the container again

P.S. What also magically refreshes the thing is randomly making any changes  to contents of the `@theme` (actually even no changes required, just saving any `.css` file already updates the thing). The whole `src` folder is included in volume, so it shouldn't be the issue. 

P.P.S I tracked the changes inside of `.next/static/css/layout.css` path. it indeed updates only when i refresh any `.css` file in the project

Update: 
I created a dummy Next.js App (sorry, should have tested with such minimal settings initially). Turns out containerization is not the issue, but it does not update in Next.js app in general.


How to reproduce:

1. Create Next.js app via `npx create-next-app@latest` and start a server
2. set two random variables in `@theme` in `global.css` file
 e.g. 
`@theme {
  --color-background: red;
  --color-foreground: pink;
}`

3. Go to any component a try to render a plain `<div />`.
4. Add a style to that div with CSS var as `<div style={{ color: "var(--color-foreground)" }}/>`
5. Notice that static CSS file is not refreshed therefore CSS Var is not added.
6. Try to save (with or without changes) any `.css` file and notice that it refreshes static CSS file
7. Try to change variable and notice that until you save `.css` file or restart the server the variable is not picked up

Update:

I've setup a dummy `vite` project and tested all the steps above. Issue still persists. Workaround currently is the same: restart `vite` server or save any `.css` file

Patch:
diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts
--- a/packages/tailwindcss/src/index.ts
+++ b/packages/tailwindcss/src/index.ts
@@ -696,6 +696,7 @@ export async function compileAst(
       }
 
       let didChange = defaultDidChange
+      let didAddExternalVariable = false
       defaultDidChange = false
 
       // Add all new candidates unless we know that they are invalid.
@@ -703,11 +704,13 @@ export async function compileAst(
       for (let candidate of newRawCandidates) {
         if (!designSystem.invalidCandidates.has(candidate)) {
           if (candidate[0] === '-' && candidate[1] === '-') {
-            designSystem.theme.markUsedVariable(candidate)
+            let didMarkVariableAsUsed = designSystem.theme.markUsedVariable(candidate)
+            didChange ||= didMarkVariableAsUsed
+            didAddExternalVariable ||= didMarkVariableAsUsed
           } else {
             allValidCandidates.add(candidate)
+            didChange ||= allValidCandidates.size !== prevSize
           }
-          didChange ||= allValidCandidates.size !== prevSize
         }
       }
 
@@ -725,7 +728,7 @@ export async function compileAst(
       // If no new ast nodes were generated, then we can return the original
       // CSS. This currently assumes that we only add new ast nodes and never
       // remove any.
-      if (previousAstNodeCount === newNodes.length) {
+      if (!didAddExternalVariable && previousAstNodeCount === newNodes.length) {
         compiled ??= optimizeAst(ast, designSystem)
         return compiled
       }
diff --git a/packages/tailwindcss/src/theme.ts b/packages/tailwindcss/src/theme.ts
--- a/packages/tailwindcss/src/theme.ts
+++ b/packages/tailwindcss/src/theme.ts
@@ -193,11 +193,13 @@ export class Theme {
     return `var(${escape(this.prefixKey(themeKey))}${fallback ? `, ${fallback}` : ''})`
   }
 
-  markUsedVariable(themeKey: string) {
+  markUsedVariable(themeKey: string): boolean {
     let key = unescape(this.#unprefixKey(themeKey))
     let value = this.values.get(key)
-    if (!value) return
+    if (!value) return false
+    let isUsed = value.options & ThemeOptions.USED
     value.options |= ThemeOptions.USED
+    return !isUsed
   }
 
   resolve(candidateValue: string | null, themeKeys: ThemeKey[]): string | null {


Test patch:
diff --git a/integrations/cli/index.test.ts b/integrations/cli/index.test.ts
--- a/integrations/cli/index.test.ts
+++ b/integrations/cli/index.test.ts
@@ -1303,11 +1303,11 @@ test(
       `,
     )
 
-    fs.expectFileToContain(
+    // prettier-ignore
+    await fs.expectFileToContain(
       './dist/out.css',
       css`
-        :root,
-        :host {
+        :root, :host {
           --color-blue-500: blue;
         }
       `,