diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a95136a0e91..361db062e60b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Vite: Ensure hot-reloading works with SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052)) - Vite: Fix a crash when starting the development server in SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052)) - Prevent camelCasing CSS custom properties added by JavaScript plugins ([#16103](https://github.com/tailwindlabs/tailwindcss/pull/16103)) +- Do not emit `@keyframes` in `@theme reference` ([#16120](https://github.com/tailwindlabs/tailwindcss/pull/16120)) ## [4.0.1] - 2025-01-29 diff --git a/integrations/cli/index.test.ts b/integrations/cli/index.test.ts index 60fc03e421f6..2e6b9ad03549 100644 --- a/integrations/cli/index.test.ts +++ b/integrations/cli/index.test.ts @@ -1196,3 +1196,65 @@ test( `) }, ) + +test( + '@theme reference should never emit values', + { + fs: { + 'package.json': json` + { + "dependencies": { + "tailwindcss": "workspace:^", + "@tailwindcss/cli": "workspace:^" + } + } + `, + 'src/index.css': css` + @reference "tailwindcss"; + + .keep-me { + color: red; + } + `, + }, + }, + async ({ fs, spawn, expect }) => { + let process = await spawn( + `pnpm tailwindcss --input src/index.css --output dist/out.css --watch`, + ) + await process.onStderr((m) => m.includes('Done in')) + + expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(` + " + --- ./dist/out.css --- + .keep-me { + color: red; + } + " + `) + + await fs.write( + './src/index.css', + css` + @reference "tailwindcss"; + + /* Not a reference! */ + @theme { + --color-pink: pink; + } + + .keep-me { + color: red; + } + `, + ) + expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(` + " + --- ./dist/out.css --- + .keep-me { + color: red; + } + " + `) + }, +) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index 8fba63cfccef..9f7962916d90 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -1530,6 +1530,75 @@ describe('Parsing themes values from CSS', () => { `) }) + test('`@keyframes` added in `@theme reference` should not be emitted', async () => { + return expect( + await compileCss( + css` + @theme reference { + --animate-foo: foo 1s infinite; + + @keyframes foo { + 0%, + 100% { + color: red; + } + 50% { + color: blue; + } + } + } + @tailwind utilities; + `, + ['animate-foo'], + ), + ).toMatchInlineSnapshot(` + ".animate-foo { + animation: var(--animate-foo); + }" + `) + }) + + test('`@keyframes` added in `@theme reference` should not be emitted, even if another `@theme` block exists', async () => { + return expect( + await compileCss( + css` + @theme reference { + --animate-foo: foo 1s infinite; + + @keyframes foo { + 0%, + 100% { + color: red; + } + 50% { + color: blue; + } + } + } + + @theme { + --color-pink: pink; + } + + @tailwind utilities; + `, + ['bg-pink', 'animate-foo'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --color-pink: pink; + } + + .animate-foo { + animation: var(--animate-foo); + } + + .bg-pink { + background-color: var(--color-pink); + }" + `) + }) + test('theme values added as reference that override existing theme value suppress the output of the original theme value as a variable', async () => { expect( await compileCss( diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index df6e09f90649..5947e73b810c 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -454,6 +454,12 @@ async function parseCss( // Collect `@keyframes` rules to re-insert with theme variables later, // since the `@theme` rule itself will be removed. if (child.kind === 'at-rule' && child.name === '@keyframes') { + // Do not track/emit `@keyframes`, if they are part of a `@theme reference`. + if (themeOptions & ThemeOptions.REFERENCE) { + replaceWith([]) + return WalkAction.Skip + } + theme.addKeyframes(child) replaceWith([]) return WalkAction.Skip