diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b09cfdb1a70..5315b9f96bfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/packages/tailwindcss/src/compat/plugin-api.test.ts b/packages/tailwindcss/src/compat/plugin-api.test.ts index ea1ab6f0d4a4..3058f9924995 100644 --- a/packages/tailwindcss/src/compat/plugin-api.test.ts +++ b/packages/tailwindcss/src/compat/plugin-api.test.ts @@ -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()', () => { diff --git a/packages/tailwindcss/src/compat/plugin-api.ts b/packages/tailwindcss/src/compat/plugin-api.ts index ca799c36050f..950302807db0 100644 --- a/packages/tailwindcss/src/compat/plugin-api.ts +++ b/packages/tailwindcss/src/compat/plugin-api.ts @@ -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))) } }