From ee987e3f6a128c94691965e3391494a17e299b46 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Tue, 26 Aug 2025 10:21:15 -0400 Subject: [PATCH] Discard `matchVariant` matches with unknown named values (#18799) This PR fixes two issues: - When a variant is defined by `matchVariant` it could match unknown values but not apply the variant (because it's unknown). This would result in a utility being output that is the _same_ as a bare utility without variants but a longer name. These were intended to be discarded but weren't done so correctly. - Similarly, when we encounter a known value but its not a string the same thing would happen where we'd output a utility without applying the variant. This was also intended to be discarded. Basically given this code: ```js matchVariant( "foo", (value) => `&:is([data-foo='${value}'])`, { values: { DEFAULT: "", bar: "bar", obj: { some: "object" }, }, } ) ``` And this HTML: ```html
``` This CSS would be produced: ```css @layer utilities { .foo-bar\:bg-none { &:is([data-foo='bar']) { background-image: none; } } /* this one shouldn't be here */ .foo-baz\:bg-none { background-image: none; } /* this one shouldn't be here */ .foo-obj\:bg-none { background-image: none; } .foo-\[baz\]\:bg-none { &:is([data-foo='baz']) { background-image: none; } } } ``` --- CHANGELOG.md | 2 + .../tailwindcss/src/compat/plugin-api.test.ts | 73 +++++++++++++++++++ packages/tailwindcss/src/compat/plugin-api.ts | 4 +- 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d198fcc13..31913f416 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Don't transition `visibility` when using `transition` ([#18795](https://github.com/tailwindlabs/tailwindcss/pull/18795)) +- Discard matched variants with unknown named values ([#18799](https://github.com/tailwindlabs/tailwindcss/pull/18799)) +- Discard matched variants with non-string values ([#18799](https://github.com/tailwindlabs/tailwindcss/pull/18799)) ## [4.1.12] - 2025-08-13 diff --git a/packages/tailwindcss/src/compat/plugin-api.test.ts b/packages/tailwindcss/src/compat/plugin-api.test.ts index 3058f9924..4e35afa5a 100644 --- a/packages/tailwindcss/src/compat/plugin-api.test.ts +++ b/packages/tailwindcss/src/compat/plugin-api.test.ts @@ -2778,6 +2778,79 @@ describe('matchVariant', () => { }" `) }) + + test('ignores variants that use unknown values', async () => { + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + path: '', + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('foo', (flavor) => `&:is(${flavor})`, { + values: { + known: 'known', + }, + }) + }, + } + }, + }, + ) + + let compiled = build(['foo-[test]:flex', 'foo-known:flex', 'foo-unknown:flex']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { + .foo-known\\:flex:is(known), .foo-\\[test\\]\\:flex:is(test) { + display: flex; + } + }" + `) + }) + + test('ignores variants that produce non-string values', async () => { + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + path: '', + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('foo', (flavor) => `&:is(${flavor})`, { + values: { + string: 'some string', + object: { some: 'object' }, + }, + }) + }, + } + }, + }, + ) + + let compiled = build(['foo-[test]:flex', 'foo-string:flex', 'foo-object:flex']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { + .foo-string\\:flex:is(some string), .foo-\\[test\\]\\:flex:is(test) { + display: flex; + } + }" + `) + }) }) describe('addUtilities()', () => { diff --git a/packages/tailwindcss/src/compat/plugin-api.ts b/packages/tailwindcss/src/compat/plugin-api.ts index 950302807..321a245ee 100644 --- a/packages/tailwindcss/src/compat/plugin-api.ts +++ b/packages/tailwindcss/src/compat/plugin-api.ts @@ -203,10 +203,12 @@ export function buildPluginApi({ } else if (variant.value.kind === 'named' && options?.values) { let defaultValue = options.values[variant.value.value] if (typeof defaultValue !== 'string') { - return + return null } ruleNodes.nodes = resolveVariantValue(defaultValue, variant.modifier, ruleNodes.nodes) + } else { + return null } }) },