Don’t output CSS objects with false or undefined in the AST (#18571)

Fixes https://github.com/tailwindlabs/tailwindcss-typography/issues/384

Basically when addUtilities/addComponents/matchUtilities/matchComponents
saw a value of `false` it was being output instead of being discarded
like it was in v3.

The types really require these to be strings but for things like the
typography plugin this isn't really carried through from its theme
config so it was easy to put anything in there and not realize it
doesn't match the expected types.

Basically this:
```js
addUtilities({
  '.foo': {
    a: 'red',
    'z-index': 0,
    '.bar': false,
    '.baz': null, // this one already worked
    '.qux': undefined,
  },
})
```

Now works like it did in v3 and omits `.bar`, `.baz`, and `.qux`
This commit is contained in:
Jordan Pittman 2025-07-21 15:39:34 -04:00 • committed by GitHub
parent 847ed1e4d7
commit fa3f45f02c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 48 additions and 1 deletions

View file

@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Center the dropdown icon added to an input with a paired datalist ([#18511](https://github.com/tailwindlabs/tailwindcss/pull/18511))
- Extract candidates in Slang templates ([#18565](https://github.com/tailwindlabs/tailwindcss/pull/18565))
- Improve error messages when encountering invalid functional utility names ([#18568](https://github.com/tailwindlabs/tailwindcss/pull/18568))
- Don’t output CSS objects with false or undefined in the AST ([#18571](https://github.com/tailwindlabs/tailwindcss/pull/18571))
## [4.1.11] - 2025-06-26

View file

@ -3360,6 +3360,45 @@ describe('addUtilities()', () => {
}"
`)
})
test('values that are `false`, `null`, or `undefined` are discarded from CSS object ASTs', async () => {
let compiled = await compile(
css`
@plugin "my-plugin";
@tailwind utilities;
`,
{
async loadModule(id, base) {
return {
path: '',
base,
module: ({ addUtilities }: PluginAPI) => {
addUtilities({
'.foo': {
a: 'red',
// @ts-ignore: While this isn't valid per the types this did work in v3
'z-index': 0,
// @ts-ignore
'.bar': false,
// @ts-ignore
'.baz': null,
// @ts-ignore
'.qux': undefined,
},
})
},
}
},
},
)
expect(compiled.build(['foo']).trim()).toMatchInlineSnapshot(`
".foo {
a: red;
z-index: 0;
}"
`)
})
})
describe('matchUtilities()', () => {

View file

@ -540,6 +540,13 @@ export function objectToAst(rules: CssInJs | CssInJs[]): AstNode[] {
let entries = rules.flatMap((rule) => Object.entries(rule))
for (let [name, value] of entries) {
if (value === null || value === undefined) continue
// @ts-expect-error
// We do not want `false` present in the types but still need to discard these nodes for
// compatibility purposes
if (value === false) continue
if (typeof value !== 'object') {
if (!name.startsWith('--')) {
if (value === '@slot') {
@ -561,7 +568,7 @@ export function objectToAst(rules: CssInJs | CssInJs[]): AstNode[] {
ast.push(rule(name, objectToAst(item)))
}
}
} else if (value !== null) {
} else {
ast.push(rule(name, objectToAst(value)))
}
}