From 706a597735f9715908b14ed9624aa6c7b6f40b11 Mon Sep 17 00:00:00 2001 From: koreahghg Date: Thu, 2 Jul 2026 15:10:52 +0900 Subject: [PATCH 1/5] Fix theme() in JS plugins returning unresolved object instead of DEFAULT value --- CHANGELOG.md | 1 + .../tailwindcss/src/compat/plugin-api.test.ts | 40 +++++++++++++++++++ .../src/compat/plugin-functions.ts | 17 ++++++++ 3 files changed, 58 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6866fb886c0..323828731dc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Allow `@tailwindcss/cli` in `--watch` mode to use polling with `--poll` when filesystem events are unreliable or unavailable ([#20297](https://github.com/tailwindlabs/tailwindcss/pull/20297)) - Canonicalization: match arbitrary hex colors against theme colors case-insensitively (e.g. `bg-[#fff]` and `bg-[#FFF]` → `bg-white`) ([#20298](https://github.com/tailwindlabs/tailwindcss/pull/20298)) - Prevent Preflight from overriding Firefox's native `iframe:focus-visible` outline styles ([#20292](https://github.com/tailwindlabs/tailwindcss/pull/20292)) +- Fix `theme()` in JS plugins returning an unresolved object instead of the `DEFAULT` value when a CSS theme key shares a prefix with a sibling key (e.g. `--color-foo` and `--color-foo-bar`) ## [4.3.2] - 2026-06-26 diff --git a/packages/tailwindcss/src/compat/plugin-api.test.ts b/packages/tailwindcss/src/compat/plugin-api.test.ts index e3ea3fb07492..2ecdbe9a3202 100644 --- a/packages/tailwindcss/src/compat/plugin-api.test.ts +++ b/packages/tailwindcss/src/compat/plugin-api.test.ts @@ -635,6 +635,46 @@ describe('theme', async () => { }) }) + test('theme() resolves the DEFAULT value when a bare CSS theme key shares a prefix with a sibling key', async () => { + expect( + await run( + ['example-foo', 'example-foo-bar'], + css` + @tailwind utilities; + @theme { + --color-foo: red; + --color-foo-bar: blue; + } + @plugin "my-plugin"; + `, + { + loadModule: async (_id, base) => { + return { + path: '', + base, + module: plugin(function ({ addUtilities, theme }) { + addUtilities({ + '.example-foo': { color: theme('colors.foo') }, + '.example-foo-bar': { color: theme('colors.foo-bar') }, + }) + }), + } + }, + }, + ), + ).toMatchInlineSnapshot(` + " + .example-foo { + color: red; + } + + .example-foo-bar { + color: #00f; + } + " + `) + }) + test('all necessary theme keys support bare values', async () => { expect( await run( diff --git a/packages/tailwindcss/src/compat/plugin-functions.ts b/packages/tailwindcss/src/compat/plugin-functions.ts index 40b8c93e89f1..e5ec4d31c1f7 100644 --- a/packages/tailwindcss/src/compat/plugin-functions.ts +++ b/packages/tailwindcss/src/compat/plugin-functions.ts @@ -100,6 +100,23 @@ export function createThemeFn( return [base, extra] } + // At this point `cssValue` is an object purely because a CSS theme key + // (e.g. `--color-foo`) shares a prefix with sibling keys (e.g. + // `--color-foo-bar`), and there was no JS config value to merge it + // with (otherwise we would have returned from the branch above). This + // is the same ambiguity handled for the CSS `theme()` function in + // `apply-compat-hooks.ts`, so resolve it the same way: prefer the + // `DEFAULT` key instead of leaking the synthetic object (and its + // internal `__CSS_VALUES__` bookkeeping) into plugin code. + if ( + cssValue !== null && + typeof cssValue === 'object' && + !Array.isArray(cssValue) && + 'DEFAULT' in cssValue + ) { + return cssValue.DEFAULT + } + // Values from CSS take precedence over values from the config return cssValue ?? configValue })() From 7a13d25cc6d67cebb5b3b6f80f414e951544d274 Mon Sep 17 00:00:00 2001 From: koreahghg Date: Thu, 2 Jul 2026 15:51:39 +0900 Subject: [PATCH 2/5] Add PR reference link to changelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 323828731dc9..5985bbed70ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Allow `@tailwindcss/cli` in `--watch` mode to use polling with `--poll` when filesystem events are unreliable or unavailable ([#20297](https://github.com/tailwindlabs/tailwindcss/pull/20297)) - Canonicalization: match arbitrary hex colors against theme colors case-insensitively (e.g. `bg-[#fff]` and `bg-[#FFF]` → `bg-white`) ([#20298](https://github.com/tailwindlabs/tailwindcss/pull/20298)) - Prevent Preflight from overriding Firefox's native `iframe:focus-visible` outline styles ([#20292](https://github.com/tailwindlabs/tailwindcss/pull/20292)) -- Fix `theme()` in JS plugins returning an unresolved object instead of the `DEFAULT` value when a CSS theme key shares a prefix with a sibling key (e.g. `--color-foo` and `--color-foo-bar`) +- Fix `theme()` in JS plugins returning an unresolved object instead of the `DEFAULT` value when a CSS theme key shares a prefix with a sibling key (e.g. `--color-foo` and `--color-foo-bar`) ([#20299](https://github.com/tailwindlabs/tailwindcss/pull/20299)) ## [4.3.2] - 2026-06-26 From cddb26743c08363ba519e3a906828cee83bc0ff8 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 2 Jul 2026 15:00:50 +0200 Subject: [PATCH 3/5] format comments --- packages/tailwindcss/src/compat/plugin-functions.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/tailwindcss/src/compat/plugin-functions.ts b/packages/tailwindcss/src/compat/plugin-functions.ts index e5ec4d31c1f7..1414fb394ac6 100644 --- a/packages/tailwindcss/src/compat/plugin-functions.ts +++ b/packages/tailwindcss/src/compat/plugin-functions.ts @@ -102,12 +102,12 @@ export function createThemeFn( // At this point `cssValue` is an object purely because a CSS theme key // (e.g. `--color-foo`) shares a prefix with sibling keys (e.g. - // `--color-foo-bar`), and there was no JS config value to merge it - // with (otherwise we would have returned from the branch above). This - // is the same ambiguity handled for the CSS `theme()` function in + // `--color-foo-bar`), and there was no JS config value to merge it with + // (otherwise we would have returned from the branch above). This is the + // same ambiguity handled for the CSS `theme()` function in // `apply-compat-hooks.ts`, so resolve it the same way: prefer the - // `DEFAULT` key instead of leaking the synthetic object (and its - // internal `__CSS_VALUES__` bookkeeping) into plugin code. + // `DEFAULT` key instead of leaking the synthetic object (and its internal + // `__CSS_VALUES__` bookkeeping) into plugin code. if ( cssValue !== null && typeof cssValue === 'object' && From 036624e8b7b7d523f6ae67f723d8f831701d3856 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 2 Jul 2026 15:14:19 +0200 Subject: [PATCH 4/5] rewrite comment --- .../tailwindcss/src/compat/plugin-functions.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/tailwindcss/src/compat/plugin-functions.ts b/packages/tailwindcss/src/compat/plugin-functions.ts index 1414fb394ac6..ee795c40f16d 100644 --- a/packages/tailwindcss/src/compat/plugin-functions.ts +++ b/packages/tailwindcss/src/compat/plugin-functions.ts @@ -100,14 +100,14 @@ export function createThemeFn( return [base, extra] } - // At this point `cssValue` is an object purely because a CSS theme key - // (e.g. `--color-foo`) shares a prefix with sibling keys (e.g. - // `--color-foo-bar`), and there was no JS config value to merge it with - // (otherwise we would have returned from the branch above). This is the - // same ambiguity handled for the CSS `theme()` function in - // `apply-compat-hooks.ts`, so resolve it the same way: prefer the - // `DEFAULT` key instead of leaking the synthetic object (and its internal - // `__CSS_VALUES__` bookkeeping) into plugin code. + // If `--color-foo` and `--color-foo-bar` are both defined, `colors.foo` + // produces a synthetic object: + // + // ```ts + // { DEFAULT: 'red', bar: 'blue', __CSS_VALUES__: { DEFAULT: 0, bar: 0 } } + // ``` + // + // Prefer `DEFAULT` instead of exposing the object to plugin code. if ( cssValue !== null && typeof cssValue === 'object' && From 2a38bd7bd2a0b9e7ba2afc7331b3eb6e96353115 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 2 Jul 2026 15:07:54 +0200 Subject: [PATCH 5/5] update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5985bbed70ce..b4058a7729bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Allow `@tailwindcss/cli` in `--watch` mode to use polling with `--poll` when filesystem events are unreliable or unavailable ([#20297](https://github.com/tailwindlabs/tailwindcss/pull/20297)) - Canonicalization: match arbitrary hex colors against theme colors case-insensitively (e.g. `bg-[#fff]` and `bg-[#FFF]` → `bg-white`) ([#20298](https://github.com/tailwindlabs/tailwindcss/pull/20298)) - Prevent Preflight from overriding Firefox's native `iframe:focus-visible` outline styles ([#20292](https://github.com/tailwindlabs/tailwindcss/pull/20292)) -- Fix `theme()` in JS plugins returning an unresolved object instead of the `DEFAULT` value when a CSS theme key shares a prefix with a sibling key (e.g. `--color-foo` and `--color-foo-bar`) ([#20299](https://github.com/tailwindlabs/tailwindcss/pull/20299)) +- Prevent `theme('colors.foo')` in JS plugins from returning an internal disambiguation object when a CSS theme key shares a prefix with a sibling key like `--color-foo-bar` ([#20299](https://github.com/tailwindlabs/tailwindcss/pull/20299)) ## [4.3.2] - 2026-06-26