diff --git a/CHANGELOG.md b/CHANGELOG.md index d198fcc130c8..31913f4169cf 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 3058f9924995..4e35afa5a84e 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 950302807db0..321a245eee3f 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 } }) },