From d733f3d168b4bbdb541c93cb089b349ee720d38a Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 14 Aug 2026 11:42:03 +0200 Subject: [PATCH 1/4] add failing test --- .../src/canonicalize-candidates.test.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/tailwindcss/src/canonicalize-candidates.test.ts b/packages/tailwindcss/src/canonicalize-candidates.test.ts index 550a9c9a24ac..567f0ad0d837 100644 --- a/packages/tailwindcss/src/canonicalize-candidates.test.ts +++ b/packages/tailwindcss/src/canonicalize-candidates.test.ts @@ -1638,4 +1638,26 @@ describe('regressions', () => { 'lg:flex', ]) }) + + // https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1610 + test('does not merge utilities whose theme variables resolve to CSS-wide keywords', async () => { + let designSystem = await __unstable__loadDesignSystem( + css` + @tailwind utilities; + @theme { + --foreground: unset; + --default: unset; + } + @theme inline { + --color-foreground: var(--foreground); + --color-default-soft-hover: color-mix(in oklab, var(--default) 60%, transparent); + } + `, + { base: __dirname }, + ) + + expect( + designSystem.canonicalizeCandidates(['text-foreground/60', 'text-default-soft-hover']), + ).toEqual(['text-foreground/60', 'text-default-soft-hover']) + }) }) From c2a25e47553cab7f5392c904a37b7f655fd86d37 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 14 Aug 2026 11:44:51 +0200 Subject: [PATCH 2/4] prevent inlining CSS-wide keywords --- .../src/canonicalize-candidates.ts | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/packages/tailwindcss/src/canonicalize-candidates.ts b/packages/tailwindcss/src/canonicalize-candidates.ts index ad2648e12af4..df81d2e7f2aa 100644 --- a/packages/tailwindcss/src/canonicalize-candidates.ts +++ b/packages/tailwindcss/src/canonicalize-candidates.ts @@ -2563,6 +2563,25 @@ function canonicalizeAst(designSystem: DesignSystem, ast: AstNode[], options: Si return ast } +// Variables whose theme value is a CSS-wide keyword (e.g.: `unset`) are never +// inlined. These are typically registered as a placeholder to be re-assigned at +// runtime, so two variables that share such a value are not interchangeable. +// +// E.g.: +// +// ```css +// @theme { +// --foreground: unset; +// --background: unset; +// } +// ``` +// +// Inlining would make `text-(--foreground)` and `text-(--background)` produce +// the same signature `color: unset`, even though they are different at runtime. +// +// https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/Data_types#css-wide_keywords +const CSS_WIDE_KEYWORDS = ['initial', 'inherit', 'revert', 'revert-layer', 'revert-rule', 'unset'] + // Resolve theme values to their inlined value. // // E.g.: @@ -2578,8 +2597,8 @@ function canonicalizeAst(designSystem: DesignSystem, ast: AstNode[], options: Si // } // ``` // -// Which conveniently will be equivalent to: `text-red-500` when we inline -// the value. +// Which conveniently will be equivalent to: `text-red-500` when we inline the +// value. // // Without inlining: // ```css @@ -2595,13 +2614,13 @@ function canonicalizeAst(designSystem: DesignSystem, ast: AstNode[], options: Si // } // ``` // -// Recently we made sure that utilities like `text-red-500` also generate -// the fallback value for usage in `@reference` mode. +// Recently we made sure that utilities like `text-red-500` also generate the +// fallback value for usage in `@reference` mode. // -// The second assumption is that if you use `var(--key, fallback)` that -// happens to match a known variable _and_ its inlined value. Then we can -// replace it with the inlined variable. This allows us to handle custom -// `@theme` and `@theme inline` definitions. +// The second assumption is that if you use `var(--key, fallback)` that happens +// to match a known variable _and_ its inlined value. Then we can replace it +// with the inlined variable. This allows us to handle custom `@theme` and +// `@theme inline` definitions. function resolveVariablesInValue(value: string, designSystem: DesignSystem): string { let changed = false let valueAst = ValueParser.parse(value) @@ -2630,6 +2649,9 @@ function resolveVariablesInValue(value: string, designSystem: DesignSystem): str seen.add(variable) if (variableValue === undefined) return // Couldn't resolve the variable + // CSS-wide keywords are never inlined + if (CSS_WIDE_KEYWORDS.includes(variableValue)) return + // Inject variable fallbacks when no fallback is present yet. // // A fallback could consist of multiple values. From 3dbc417d92c45b6f60f561a8afd9433961d4e8a9 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 14 Aug 2026 12:01:09 +0200 Subject: [PATCH 3/4] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 036c2c3b43b5..175a1daa2259 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Skip ignored directories entirely when computing watch globs (`scanner.globs`), instead of walking their full contents on every rebuild ([#20408](https://github.com/tailwindlabs/tailwindcss/pull/20408)) - Oxide: drop invalid UTF-8 candidates ([#20389](https://github.com/tailwindlabs/tailwindcss/pull/20389)) - `@tailwindcss/vite` no longer forces a full page reload for external files (e.g.: `.php` files) ([#20414](https://github.com/tailwindlabs/tailwindcss/issues/20414)) +- Canonicalization: don't merge utilities that reference different theme variables set to CSS-wide keywords like `unset` ([#20417](https://github.com/tailwindlabs/tailwindcss/pull/20417)) ## [4.3.3] - 2026-07-16 From aa2c4c8bafc5ec5d346cea819c9c8060942819c9 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 14 Aug 2026 12:07:37 +0200 Subject: [PATCH 4/4] Update packages/tailwindcss/src/canonicalize-candidates.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- packages/tailwindcss/src/canonicalize-candidates.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tailwindcss/src/canonicalize-candidates.ts b/packages/tailwindcss/src/canonicalize-candidates.ts index df81d2e7f2aa..d1dfc09dedc4 100644 --- a/packages/tailwindcss/src/canonicalize-candidates.ts +++ b/packages/tailwindcss/src/canonicalize-candidates.ts @@ -2650,7 +2650,7 @@ function resolveVariablesInValue(value: string, designSystem: DesignSystem): str if (variableValue === undefined) return // Couldn't resolve the variable // CSS-wide keywords are never inlined - if (CSS_WIDE_KEYWORDS.includes(variableValue)) return + if (CSS_WIDE_KEYWORDS.includes(variableValue.toLowerCase())) return // Inject variable fallbacks when no fallback is present yet. //