Skip to content

Commit 3b61277

Browse files
Don't crash when setting JS theme value to null (#16210)
Closes #16035 In v3 it was possible to unset a specific color namespace by setting doing something like this: ```js export default { theme: { extend: { colors: { red: null, }, }, }, } ``` This pattern would crash in v4 right now due to the theme access function not being able to work on the red property being a `null`. This PR fixes this crash. However it leaves the behavior as-is for now so that the red namespace _defined via CSS will still be accessible_. This is technically different from v3 but fixing this would be more work as we only allow unsetting top-level namespaces in the interop layer (via the non-`extend`-theme-object). I would recommend migrating to the v4 API for doing these partial namespace resets if you want to get rid of the defaults in v4: ```css @theme { --color-red-*: initial; } ``` ## Test plan The crash was mainly captured via the test in `compat/config.test.ts` but I've added two more tests across the different levels of abstractions so that it's clear what `null` should be doing. --------- Co-authored-by: Robin Malfait <[email protected]>
1 parent 5601fb5 commit 3b61277

File tree

5 files changed

+134
-1
lines changed

5 files changed

+134
-1
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Fixed
1111

12+
- Fix a crash when setting JS theme values to `null` ([#16210](https://github.com/tailwindlabs/tailwindcss/pull/16210))
1213
- Ensure CSS variables in arbitrary values are properly decoded ([#16206](https://github.com/tailwindlabs/tailwindcss/pull/16206))
1314
- Ensure that the `containers` JS theme key is added to the `--container-*` namespace ([#16169](https://github.com/tailwindlabs/tailwindcss/pull/16169))
1415
- Fix missing `@keyframes` definition ([#16237](https://github.com/tailwindlabs/tailwindcss/pull/16237))

packages/tailwindcss/src/compat/apply-config-to-theme.test.ts

+36
Original file line numberDiff line numberDiff line change
@@ -223,3 +223,39 @@ test('converts opacity modifiers from decimal to percentage values', () => {
223223
expect(theme.resolve('20', ['--opacity'])).toEqual('20%')
224224
expect(theme.resolve('25', ['--opacity'])).toEqual('25%')
225225
})
226+
227+
test('handles setting theme keys to null', async () => {
228+
let theme = new Theme()
229+
let design = buildDesignSystem(theme)
230+
231+
theme.add('--color-blue-400', 'blue', ThemeOptions.DEFAULT)
232+
theme.add('--color-blue-500', '#3b82f6')
233+
theme.add('--color-red-400', 'red', ThemeOptions.DEFAULT)
234+
theme.add('--color-red-500', '#ef4444')
235+
236+
let { resolvedConfig, replacedThemeKeys } = resolveConfig(design, [
237+
{
238+
config: {
239+
theme: {
240+
extend: {
241+
colors: {
242+
blue: null,
243+
},
244+
},
245+
},
246+
},
247+
base: '/root',
248+
reference: false,
249+
},
250+
])
251+
applyConfigToTheme(design, resolvedConfig, replacedThemeKeys)
252+
253+
expect(theme.namespace('--color')).toMatchInlineSnapshot(`
254+
Map {
255+
"blue-400" => "blue",
256+
"blue-500" => "#3b82f6",
257+
"red-400" => "red",
258+
"red-500" => "#ef4444",
259+
}
260+
`)
261+
})

packages/tailwindcss/src/compat/config.test.ts

+51
Original file line numberDiff line numberDiff line change
@@ -1628,3 +1628,54 @@ test('old theme values are merged with their renamed counterparts in the CSS the
16281628

16291629
expect(didCallPluginFn).toHaveBeenCalled()
16301630
})
1631+
1632+
test('handles setting theme keys to null', async () => {
1633+
let compiler = await compile(
1634+
css`
1635+
@theme default {
1636+
--color-red-50: oklch(0.971 0.013 17.38);
1637+
--color-red-100: oklch(0.936 0.032 17.717);
1638+
}
1639+
@config "./my-config.js";
1640+
@tailwind utilities;
1641+
@theme {
1642+
--color-red-100: oklch(0.936 0.032 17.717);
1643+
--color-red-200: oklch(0.885 0.062 18.334);
1644+
}
1645+
`,
1646+
{
1647+
loadModule: async () => {
1648+
return {
1649+
module: {
1650+
theme: {
1651+
extend: {
1652+
colors: {
1653+
red: null,
1654+
},
1655+
},
1656+
},
1657+
},
1658+
base: '/root',
1659+
}
1660+
},
1661+
},
1662+
)
1663+
1664+
expect(compiler.build(['bg-red-50', 'bg-red-100', 'bg-red-200'])).toMatchInlineSnapshot(`
1665+
":root, :host {
1666+
--color-red-50: oklch(0.971 0.013 17.38);
1667+
--color-red-100: oklch(0.936 0.032 17.717);
1668+
--color-red-200: oklch(0.885 0.062 18.334);
1669+
}
1670+
.bg-red-50 {
1671+
background-color: var(--color-red-50);
1672+
}
1673+
.bg-red-100 {
1674+
background-color: var(--color-red-100);
1675+
}
1676+
.bg-red-200 {
1677+
background-color: var(--color-red-200);
1678+
}
1679+
"
1680+
`)
1681+
})

packages/tailwindcss/src/compat/config/resolve-config.test.ts

+45
Original file line numberDiff line numberDiff line change
@@ -254,3 +254,48 @@ test('theme keys can read from the CSS theme', () => {
254254
new Set(['colors', 'accentColor', 'placeholderColor', 'caretColor', 'transitionColor']),
255255
)
256256
})
257+
258+
test('handles null as theme values', () => {
259+
let theme = new Theme()
260+
theme.add('--color-red-50', 'red')
261+
theme.add('--color-red-100', 'red')
262+
263+
let design = buildDesignSystem(theme)
264+
265+
let { resolvedConfig, replacedThemeKeys } = resolveConfig(design, [
266+
{
267+
config: {
268+
theme: {
269+
colors: ({ theme }) => ({
270+
// Reads from the --color-* namespace
271+
...theme('color'),
272+
}),
273+
},
274+
},
275+
base: '/root',
276+
reference: false,
277+
},
278+
{
279+
config: {
280+
theme: {
281+
extend: {
282+
colors: {
283+
red: null,
284+
},
285+
},
286+
},
287+
},
288+
base: '/root',
289+
reference: false,
290+
},
291+
])
292+
293+
expect(resolvedConfig).toMatchObject({
294+
theme: {
295+
colors: {
296+
red: null,
297+
},
298+
},
299+
})
300+
expect(replacedThemeKeys).toEqual(new Set(['colors']))
301+
})

packages/tailwindcss/src/compat/plugin-functions.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ function get(obj: any, path: string[]) {
224224
let key = path[i]
225225

226226
// The key does not exist so concatenate it with the next key
227-
if (obj[key] === undefined) {
227+
if (obj?.[key] === undefined) {
228228
if (path[i + 1] === undefined) {
229229
return undefined
230230
}

0 commit comments

Comments
 (0)