From 91eed6db4d1f20d27d3d0cf5000e8b7f2fc7032a Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 9 Oct 2025 14:52:30 +0200 Subject: [PATCH 1/6] make TypeScript happy by adding missing config property --- .../src/codemods/css/migrate-media-screen.ts | 2 +- .../tailwindcss/src/compat/config.test.ts | 32 +++++++++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/packages/@tailwindcss-upgrade/src/codemods/css/migrate-media-screen.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-media-screen.ts index f4526209d89a..c10e9ee99094 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/css/migrate-media-screen.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-media-screen.ts @@ -16,7 +16,7 @@ export function migrateMediaScreen({ if (!designSystem || !userConfig) return let { resolvedConfig } = resolveConfig(designSystem, [ - { base: '', config: userConfig, reference: false }, + { base: '', config: userConfig, reference: false, src: undefined }, ]) let screens = resolvedConfig?.theme?.screens || {} diff --git a/packages/tailwindcss/src/compat/config.test.ts b/packages/tailwindcss/src/compat/config.test.ts index 196115d6357e..45ec979de112 100644 --- a/packages/tailwindcss/src/compat/config.test.ts +++ b/packages/tailwindcss/src/compat/config.test.ts @@ -12,7 +12,7 @@ test('Config files can add content', async () => { ` let compiler = await compile(input, { - loadModule: async () => ({ module: { content: ['./file.txt'] }, base: '/root' }), + loadModule: async () => ({ module: { content: ['./file.txt'] }, base: '/root', path: '' }), }) expect(compiler.sources).toEqual([{ base: '/root', pattern: './file.txt', negated: false }]) @@ -25,7 +25,7 @@ test('Config files can change dark mode (media)', async () => { ` let compiler = await compile(input, { - loadModule: async () => ({ module: { darkMode: 'media' }, base: '/root' }), + loadModule: async () => ({ module: { darkMode: 'media' }, base: '/root', path: '' }), }) expect(compiler.build(['dark:underline'])).toMatchInlineSnapshot(` @@ -45,7 +45,7 @@ test('Config files can change dark mode (selector)', async () => { ` let compiler = await compile(input, { - loadModule: async () => ({ module: { darkMode: 'selector' }, base: '/root' }), + loadModule: async () => ({ module: { darkMode: 'selector' }, base: '/root', path: '' }), }) expect(compiler.build(['dark:underline'])).toMatchInlineSnapshot(` @@ -68,6 +68,7 @@ test('Config files can change dark mode (variant)', async () => { loadModule: async () => ({ module: { darkMode: ['variant', '&:where(:not(.light))'] }, base: '/root', + path: '', }), }) @@ -101,6 +102,7 @@ test('Config files can add plugins', async () => { ], }, base: '/root', + path: '', }), }) @@ -128,6 +130,7 @@ test('Plugins loaded from config files can contribute to the config', async () = ], }, base: '/root', + path: '', }), }) @@ -157,6 +160,7 @@ test('Config file presets can contribute to the config', async () => { ], }, base: '/root', + path: '', }), }) @@ -198,6 +202,7 @@ test('Config files can affect the theme', async () => { ], }, base: '/root', + path: '', }), }) @@ -231,6 +236,7 @@ test('Variants in CSS overwrite variants from plugins', async () => { ], }, base: '/root', + path: '', }), }) @@ -317,6 +323,7 @@ describe('theme callbacks', () => { ], } satisfies Config, base: '/root', + path: '', }), }) @@ -391,6 +398,7 @@ describe('theme overrides order', () => { }, }, base: '/root', + path: '', }), }) @@ -442,6 +450,7 @@ describe('theme overrides order', () => { }, } satisfies Config, base: '/root', + path: '', } } else { return { @@ -460,6 +469,7 @@ describe('theme overrides order', () => { ) }), base: '/root', + path: '', } } }, @@ -562,6 +572,7 @@ describe('default font family compatibility', () => { }, }, base: '/root', + path: '', }), }) @@ -596,6 +607,7 @@ describe('default font family compatibility', () => { }, }, base: '/root', + path: '', }), }) @@ -631,6 +643,7 @@ describe('default font family compatibility', () => { }, }, base: '/root', + path: '', }), }) @@ -669,6 +682,7 @@ describe('default font family compatibility', () => { }, }, base: '/root', + path: '', }), }) @@ -708,6 +722,7 @@ describe('default font family compatibility', () => { }, }, base: '/root', + path: '', }), }) @@ -745,6 +760,7 @@ describe('default font family compatibility', () => { }, }, base: '/root', + path: '', }), }) @@ -779,6 +795,7 @@ describe('default font family compatibility', () => { }, }, base: '/root', + path: '', }), }) @@ -806,6 +823,7 @@ describe('default font family compatibility', () => { }, }, base: '/root', + path: '', }), }) @@ -840,6 +858,7 @@ describe('default font family compatibility', () => { }, }, base: '/root', + path: '', }), }) @@ -875,6 +894,7 @@ describe('default font family compatibility', () => { }, }, base: '/root', + path: '', }), }) @@ -913,6 +933,7 @@ describe('default font family compatibility', () => { }, }, base: '/root', + path: '', }), }) @@ -952,6 +973,7 @@ describe('default font family compatibility', () => { }, }, base: '/root', + path: '', }), }) @@ -989,6 +1011,7 @@ describe('default font family compatibility', () => { }, }, base: '/root', + path: '', }), }) @@ -1021,6 +1044,7 @@ test('creates variants for `data`, `supports`, and `aria` theme options at the s }, }, base: '/root', + path: '', }), }) @@ -1113,6 +1137,7 @@ test('merges css breakpoints with js config screens', async () => { }, }, base: '/root', + path: '', }), }) @@ -1596,6 +1621,7 @@ test('handles setting theme keys to null', async () => { }, }, base: '/root', + path: '', } }, }, From a3b5b2622c9b03d6584d811b3c01c5e0d95e36af Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 9 Oct 2025 15:17:52 +0200 Subject: [PATCH 2/6] add failing test --- .../tailwindcss/src/compat/config.test.ts | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/packages/tailwindcss/src/compat/config.test.ts b/packages/tailwindcss/src/compat/config.test.ts index 45ec979de112..5d754b6267c5 100644 --- a/packages/tailwindcss/src/compat/config.test.ts +++ b/packages/tailwindcss/src/compat/config.test.ts @@ -217,6 +217,44 @@ test('Config files can affect the theme', async () => { `) }) +// https://github.com/tailwindlabs/tailwindcss/issues/19091 +test('Accessing (nested) colors via CSS should work as expected', async () => { + let input = css` + @tailwind utilities; + @config "./config.js"; + + .example { + color: theme('colors.foo.foo-bar'); + border-color: theme('colors.foo.foo'); + } + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + colors: { + foo: { + foo: 'var(--foo-foo)', + 'foo-bar': 'var(--foo-foo-bar)', + }, + }, + }, + }, + base: '/root', + path: '', + }), + }) + + expect(compiler.build([])).toMatchInlineSnapshot(` + ".example { + color: var(--foo-foo-bar); + border-color: var(--foo-foo); + } + " + `) +}) + test('Variants in CSS overwrite variants from plugins', async () => { let input = css` @tailwind utilities; From 8acc9ff2ad393d9e5e44cd21bcb8e59e1f669144 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 9 Oct 2025 15:17:17 +0200 Subject: [PATCH 3/6] resolve `DEFAULT` value from legacy configs --- .../src/compat/apply-compat-hooks.ts | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/packages/tailwindcss/src/compat/apply-compat-hooks.ts b/packages/tailwindcss/src/compat/apply-compat-hooks.ts index 78c7d707877e..2f88ed800307 100644 --- a/packages/tailwindcss/src/compat/apply-compat-hooks.ts +++ b/packages/tailwindcss/src/compat/apply-compat-hooks.ts @@ -309,15 +309,29 @@ function upgradeToFullPluginSupport({ let resolvedValue = sharedPluginApi.theme(path, undefined) + // When a tuple is returned, return the first element if (Array.isArray(resolvedValue) && resolvedValue.length === 2) { - // When a tuple is returned, return the first element return resolvedValue[0] - } else if (Array.isArray(resolvedValue)) { - // Arrays get serialized into a comma-separated lists + } + + // Arrays get serialized into a comma-separated lists + else if (Array.isArray(resolvedValue)) { return resolvedValue.join(', ') - } else if (typeof resolvedValue === 'string') { - // Otherwise only allow string values here, objects (and namespace maps) - // are treated as non-resolved values for the CSS `theme()` function. + } + + // If we're dealing with an object that has the `DEFAULT` key, return the + // default value + else if ( + typeof resolvedValue === 'object' && + resolvedValue !== null && + 'DEFAULT' in resolvedValue + ) { + return resolvedValue.DEFAULT + } + + // Otherwise only allow string values here, objects (and namespace maps) + // are treated as non-resolved values for the CSS `theme()` function. + else if (typeof resolvedValue === 'string') { return resolvedValue } } From 0ac54323ef2ad9290878e835090c7818d42da876 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 9 Oct 2025 15:20:45 +0200 Subject: [PATCH 4/6] simplify test --- packages/tailwindcss/src/compat/config.test.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/tailwindcss/src/compat/config.test.ts b/packages/tailwindcss/src/compat/config.test.ts index 5d754b6267c5..8256f663ca17 100644 --- a/packages/tailwindcss/src/compat/config.test.ts +++ b/packages/tailwindcss/src/compat/config.test.ts @@ -218,14 +218,14 @@ test('Config files can affect the theme', async () => { }) // https://github.com/tailwindlabs/tailwindcss/issues/19091 -test('Accessing (nested) colors via CSS should work as expected', async () => { +test('Accessing a default color if a sub-color exists via CSS should work as expected', async () => { let input = css` @tailwind utilities; @config "./config.js"; .example { - color: theme('colors.foo.foo-bar'); - border-color: theme('colors.foo.foo'); + color: theme('colors.foo-bar'); + border-color: theme('colors.foo'); } ` @@ -233,11 +233,15 @@ test('Accessing (nested) colors via CSS should work as expected', async () => { loadModule: async () => ({ module: { theme: { + // Internally this object gets converted to something like: + // ``` + // { + // foo: { DEFAULT: 'var(--foo-foo)', bar: 'var(--foo-foo-bar)' }, + // } + // ``` colors: { - foo: { - foo: 'var(--foo-foo)', - 'foo-bar': 'var(--foo-foo-bar)', - }, + foo: 'var(--foo-foo)', + 'foo-bar': 'var(--foo-foo-bar)', }, }, }, From a0ae0f61390300114170169ba8d586c5839ac549 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 9 Oct 2025 16:01:37 +0200 Subject: [PATCH 5/6] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98231ff237fb..d07d3ef5b7d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix Safari devtools rendering issue due to `color-mix` fallback ([#19069](https://github.com/tailwindlabs/tailwindcss/pull/19069)) - Suppress Lightning CSS warnings about `:deep`, `:slotted` and `:global` ([#19094](https://github.com/tailwindlabs/tailwindcss/pull/19094)) +- Fix resolving colors via `theme(…)` in compat mode with nested objects ([#19097](https://github.com/tailwindlabs/tailwindcss/pull/19097)) ## [4.1.14] - 2025-10-01 From 5485e1542b69f3651478c174eef9941c904da5c0 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 9 Oct 2025 19:06:06 +0200 Subject: [PATCH 6/6] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d07d3ef5b7d9..a84ace364968 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix Safari devtools rendering issue due to `color-mix` fallback ([#19069](https://github.com/tailwindlabs/tailwindcss/pull/19069)) - Suppress Lightning CSS warnings about `:deep`, `:slotted` and `:global` ([#19094](https://github.com/tailwindlabs/tailwindcss/pull/19094)) -- Fix resolving colors via `theme(…)` in compat mode with nested objects ([#19097](https://github.com/tailwindlabs/tailwindcss/pull/19097)) +- Fix resolving theme keys when starting with the name of another theme key in JS configs and plugins ([#19097](https://github.com/tailwindlabs/tailwindcss/pull/19097)) ## [4.1.14] - 2025-10-01