From fa3f45f02c7153876f0e01088b4b5b27bd8e5e96 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 21 Jul 2025 15:39:34 -0400 Subject: [PATCH] =?UTF-8?q?Don=E2=80=99t=20output=20CSS=20objects=20with?= =?UTF-8?q?=20`false`=20or=20`undefined`=20in=20the=20AST=20(#18571)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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` --- CHANGELOG.md | 1 + .../tailwindcss/src/compat/plugin-api.test.ts | 39 +++++++++++++++++++ packages/tailwindcss/src/compat/plugin-api.ts | 9 ++++- 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b09cfdb1..5315b9f96 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 ea1ab6f0d..3058f9924 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 ca799c360..950302807 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))) } }