diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a405a1c476b..99b35a7d755e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Substitute `@variant` inside legacy JS APIs ([#19263](https://github.com/tailwindlabs/tailwindcss/pull/19263)) + ### Added - _Experimental_: Add `@container-size` utility ([#18901](https://github.com/tailwindlabs/tailwindcss/pull/18901)) diff --git a/packages/tailwindcss/src/design-system.ts b/packages/tailwindcss/src/design-system.ts index d533743c172b..384746b57b17 100644 --- a/packages/tailwindcss/src/design-system.ts +++ b/packages/tailwindcss/src/design-system.ts @@ -23,7 +23,7 @@ import { Theme, ThemeOptions, type ThemeKey } from './theme' import { Utilities, createUtilities, withAlpha } from './utilities' import { DefaultMap } from './utils/default-map' import { extractUsedVariables } from './utils/variables' -import { Variants, createVariants } from './variants' +import { Variants, createVariants, substituteAtVariant } from './variants' export const enum CompileAstFlags { None = 0, @@ -77,15 +77,21 @@ export function buildDesignSystem(theme: Theme): DesignSystem { return new DefaultMap((candidate) => { let ast = compileAstNodes(candidate, designSystem, flags) - // Arbitrary values (`text-[theme(--color-red-500)]`) and arbitrary - // properties (`[--my-var:theme(--color-red-500)]`) can contain function - // calls so we need evaluate any functions we find there that weren't in - // the source CSS. try { + // Arbitrary values (`text-[theme(--color-red-500)]`) and arbitrary + // properties (`[--my-var:theme(--color-red-500)]`) can contain function + // calls so we need evaluate any functions we find there that weren't in + // the source CSS. substituteFunctions( ast.map(({ node }) => node), designSystem, ) + + // JS plugins might contain an `@variant` inside a generated utility + substituteAtVariant( + ast.map(({ node }) => node), + designSystem, + ) } catch (err) { // If substitution fails then the candidate likely contains a call to // `theme()` that is invalid which may be because of incorrect usage, diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index f1dad6ccda4e..5cd5282b00f7 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -4665,6 +4665,60 @@ test('addBase', async () => { `) }) +test('JS APIs support @variant', async () => { + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer base, utilities; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async () => ({ + path: '', + base: '/root', + module: ({ addBase, addUtilities, matchUtilities }: PluginAPI) => { + addBase({ body: { '@variant dark': { color: 'red' } } }) + addUtilities({ '.foo': { '@variant dark': { '--foo': 'foo' } } }) + matchUtilities( + { bar: (value) => ({ '@variant dark': { '--bar': value } }) }, + { values: { one: '1' } }, + ) + }, + }), + }, + ) + + let compiled = build(['underline', 'foo', 'bar-one']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer base { + @media (prefers-color-scheme: dark) { + body { + color: red; + } + } + } + + @layer utilities { + .underline { + text-decoration-line: underline; + } + + @media (prefers-color-scheme: dark) { + .bar-one { + --bar: 1; + } + + .foo { + --foo: foo; + } + } + }" + `) +}) + it("should error when `layer(…)` is used, but it's not the first param", async () => { await expect(async () => { return await compileCss(