Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 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

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