Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 || {}

Expand Down
26 changes: 20 additions & 6 deletions packages/tailwindcss/src/compat/apply-compat-hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down
74 changes: 71 additions & 3 deletions packages/tailwindcss/src/compat/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }])
Expand All @@ -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(`
Expand All @@ -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(`
Expand All @@ -68,6 +68,7 @@ test('Config files can change dark mode (variant)', async () => {
loadModule: async () => ({
module: { darkMode: ['variant', '&:where(:not(.light))'] },
base: '/root',
path: '',
}),
})

Expand Down Expand Up @@ -101,6 +102,7 @@ test('Config files can add plugins', async () => {
],
},
base: '/root',
path: '',
}),
})

Expand Down Expand Up @@ -128,6 +130,7 @@ test('Plugins loaded from config files can contribute to the config', async () =
],
},
base: '/root',
path: '',
}),
})

Expand Down Expand Up @@ -157,6 +160,7 @@ test('Config file presets can contribute to the config', async () => {
],
},
base: '/root',
path: '',
}),
})

Expand Down Expand Up @@ -198,6 +202,7 @@ test('Config files can affect the theme', async () => {
],
},
base: '/root',
path: '',
}),
})

Expand All @@ -212,6 +217,48 @@ test('Config files can affect the theme', async () => {
`)
})

// https://github.com/tailwindlabs/tailwindcss/issues/19091
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-bar');
border-color: theme('colors.foo');
}
`

let compiler = await compile(input, {
loadModule: async () => ({
module: {
theme: {
// Internally this object gets converted to something like:
// ```
// {
// foo: { DEFAULT: 'var(--foo-foo)', bar: 'var(--foo-foo-bar)' },
// }
// ```
colors: {
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;
Expand All @@ -231,6 +278,7 @@ test('Variants in CSS overwrite variants from plugins', async () => {
],
},
base: '/root',
path: '',
}),
})

Expand Down Expand Up @@ -317,6 +365,7 @@ describe('theme callbacks', () => {
],
} satisfies Config,
base: '/root',
path: '',
}),
})

Expand Down Expand Up @@ -391,6 +440,7 @@ describe('theme overrides order', () => {
},
},
base: '/root',
path: '',
}),
})

Expand Down Expand Up @@ -442,6 +492,7 @@ describe('theme overrides order', () => {
},
} satisfies Config,
base: '/root',
path: '',
}
} else {
return {
Expand All @@ -460,6 +511,7 @@ describe('theme overrides order', () => {
)
}),
base: '/root',
path: '',
}
}
},
Expand Down Expand Up @@ -562,6 +614,7 @@ describe('default font family compatibility', () => {
},
},
base: '/root',
path: '',
}),
})

Expand Down Expand Up @@ -596,6 +649,7 @@ describe('default font family compatibility', () => {
},
},
base: '/root',
path: '',
}),
})

Expand Down Expand Up @@ -631,6 +685,7 @@ describe('default font family compatibility', () => {
},
},
base: '/root',
path: '',
}),
})

Expand Down Expand Up @@ -669,6 +724,7 @@ describe('default font family compatibility', () => {
},
},
base: '/root',
path: '',
}),
})

Expand Down Expand Up @@ -708,6 +764,7 @@ describe('default font family compatibility', () => {
},
},
base: '/root',
path: '',
}),
})

Expand Down Expand Up @@ -745,6 +802,7 @@ describe('default font family compatibility', () => {
},
},
base: '/root',
path: '',
}),
})

Expand Down Expand Up @@ -779,6 +837,7 @@ describe('default font family compatibility', () => {
},
},
base: '/root',
path: '',
}),
})

Expand Down Expand Up @@ -806,6 +865,7 @@ describe('default font family compatibility', () => {
},
},
base: '/root',
path: '',
}),
})

Expand Down Expand Up @@ -840,6 +900,7 @@ describe('default font family compatibility', () => {
},
},
base: '/root',
path: '',
}),
})

Expand Down Expand Up @@ -875,6 +936,7 @@ describe('default font family compatibility', () => {
},
},
base: '/root',
path: '',
}),
})

Expand Down Expand Up @@ -913,6 +975,7 @@ describe('default font family compatibility', () => {
},
},
base: '/root',
path: '',
}),
})

Expand Down Expand Up @@ -952,6 +1015,7 @@ describe('default font family compatibility', () => {
},
},
base: '/root',
path: '',
}),
})

Expand Down Expand Up @@ -989,6 +1053,7 @@ describe('default font family compatibility', () => {
},
},
base: '/root',
path: '',
}),
})

Expand Down Expand Up @@ -1021,6 +1086,7 @@ test('creates variants for `data`, `supports`, and `aria` theme options at the s
},
},
base: '/root',
path: '',
}),
})

Expand Down Expand Up @@ -1113,6 +1179,7 @@ test('merges css breakpoints with js config screens', async () => {
},
},
base: '/root',
path: '',
}),
})

Expand Down Expand Up @@ -1596,6 +1663,7 @@ test('handles setting theme keys to null', async () => {
},
},
base: '/root',
path: '',
}
},
},
Expand Down