Skip to content

Commit

Permalink
Make @reference emit variable fallbacks instead of CSS variable dec…
Browse files Browse the repository at this point in the history
…larations (#16774)

Fixes #16725

When using `@reference "tailwindcss";` inside a separate CSS root (e.g.
Svelte `<style>` components, CSS modules, etc.), we have no guarantee
that the CSS variables will be defined in the main stylesheet (or if
there even is one). To work around potential issues with this we decided
in #16676 that we would emit all used CSS variables from the `@theme`
inside the `@reference` block.

However, this is not only a bit surprising but also unexpected in CSS
modules and Next.js that **requires CSS module files to only create
scope-able declarations**. To fix this issue, we decided to not emit CSS
variables but instead ensure all `var(…)` calls we create for theme
values in reference mode will simply have their fallback value added.

This ensures styles work as-expected even if the root Tailwind file does
not pick up the variable as being used or _if you don't add a root at
all_. Furthermore we do not duplicate any variable declarations across
your stylesheets and you still have the ability to change variables at
runtime.

## Test plan

- Updated snapshots everywhere (see diff)
- New Next.js CSS modules integration test
  • Loading branch information
philipp-spiess authored Feb 25, 2025
1 parent 59e003e commit b389483
Show file tree
Hide file tree
Showing 22 changed files with 201 additions and 205 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed

- Vite: Don't crash when importing a virtual module in JavaScript that ends in `.css` ([#16780](https://github.com/tailwindlabs/tailwindcss/pull/16780))
- Ensure `@reference "…"` does not emit CSS variables ([#16774](https://github.com/tailwindlabs/tailwindcss/pull/16774))
- Fix an issue where `@reference "…"` would sometimes omit keyframe animations ([#16774](https://github.com/tailwindlabs/tailwindcss/pull/16774))

## [4.0.8] - 2025-02-21

Expand Down
6 changes: 3 additions & 3 deletions integrations/cli/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ test(
}
`,
'index.css': css`
@import 'tailwindcss/theme' theme(reference);
@reference 'tailwindcss/theme';
/* (1) */
/* - Only './src' should be auto-scanned, not the current working directory */
Expand Down Expand Up @@ -774,7 +774,7 @@ test(
}
`,
'project-a/src/index.css': css`
@import 'tailwindcss/theme' theme(reference);
@reference 'tailwindcss/theme';
/* Run auto-content detection in ../../project-b */
@import 'tailwindcss/utilities' source('../../project-b');
Expand Down Expand Up @@ -1132,7 +1132,7 @@ test(
}
`,
'index.css': css`
@import 'tailwindcss/theme' theme(reference);
@reference 'tailwindcss/theme';
/* (1) */
/* - Only './src' should be auto-scanned, not the current working directory */
Expand Down
2 changes: 1 addition & 1 deletion integrations/cli/standalone.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ test(
<div class="aspect-w-16"></div>
`,
'src/index.css': css`
@import 'tailwindcss/theme' theme(reference);
@reference 'tailwindcss/theme';
@import 'tailwindcss/utilities';
@plugin '@tailwindcss/forms';
Expand Down
6 changes: 3 additions & 3 deletions integrations/postcss/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,7 @@ test(
}
`,
'index.css': css`
@import 'tailwindcss/theme' theme(reference);
@reference 'tailwindcss/theme';
/* (1) */
/* - Only './src' should be auto-scanned, not the current working directory */
Expand Down Expand Up @@ -799,7 +799,7 @@ test(
}
`,
'project-a/src/index.css': css`
@import 'tailwindcss/theme' theme(reference);
@reference 'tailwindcss/theme';
/* Run auto-content detection in ../../project-b */
@import 'tailwindcss/utilities' source('../../project-b');
Expand Down Expand Up @@ -1163,7 +1163,7 @@ test(
}
`,
'index.css': css`
@import 'tailwindcss/theme' theme(reference);
@reference 'tailwindcss/theme';
/* (1) */
/* - Only './src' should be auto-scanned, not the current working directory */
Expand Down
2 changes: 1 addition & 1 deletion integrations/postcss/multi-root.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ test(
<div class="one:underline two:underline"></div>
`,
'src/shared.css': css`
@import 'tailwindcss/theme' theme(reference);
@reference 'tailwindcss/theme';
@import 'tailwindcss/utilities';
`,
'src/root1.css': css`
Expand Down
87 changes: 48 additions & 39 deletions integrations/postcss/next.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,13 @@ test(
}
`,
'postcss.config.mjs': js`
/** @type {import('postcss-load-config').Config} */
const config = {
export default {
plugins: {
'@tailwindcss/postcss': {},
},
}
export default config
`,
'next.config.mjs': js`
/** @type {import('next').NextConfig} */
const nextConfig = {}
export default nextConfig
`,
'next.config.mjs': js`export default {}`,
'app/layout.js': js`
import './globals.css'
Expand All @@ -46,12 +38,21 @@ test(
}
`,
'app/page.js': js`
import styles from './page.module.css'
export default function Page() {
return <h1 className="text-3xl font-bold underline">Hello, Next.js!</h1>
return (
<h1 className={styles.heading + ' text-3xl font-bold underline'}>Hello, Next.js!</h1>
)
}
`,
'app/page.module.css': css`
@reference './globals.css';
.heading {
@apply text-red-500 animate-ping;
}
`,
'app/globals.css': css`
@import 'tailwindcss/theme' theme(reference);
@reference 'tailwindcss/theme';
@import 'tailwindcss/utilities';
`,
},
Expand All @@ -60,14 +61,26 @@ test(
await exec('pnpm next build')

let files = await fs.glob('.next/static/css/**/*.css')
expect(files).toHaveLength(1)
let [filename] = files[0]
expect(files).toHaveLength(2)

await fs.expectFileToContain(filename, [
let globalCss: string | null = null
let moduleCss: string | null = null
for (let [filename, content] of files) {
if (content.includes('@keyframes page_ping')) moduleCss = filename
else globalCss = filename
}

await fs.expectFileToContain(globalCss!, [
candidate`underline`,
candidate`font-bold`,
candidate`text-3xl`,
])

await fs.expectFileToContain(moduleCss!, [
'color:var(--color-red-500,oklch(.637 .237 25.331)',
'animation:var(--animate-ping,ping 1s cubic-bezier(0,0,.2,1) infinite)',
/@keyframes page_ping.*{75%,to{transform:scale\(2\);opacity:0}/,
])
},
)

Expand All @@ -90,21 +103,13 @@ describe.each(['turbo', 'webpack'])('%s', (bundler) => {
}
`,
'postcss.config.mjs': js`
/** @type {import('postcss-load-config').Config} */
const config = {
export default {
plugins: {
'@tailwindcss/postcss': {},
},
}
export default config
`,
'next.config.mjs': js`
/** @type {import('next').NextConfig} */
const nextConfig = {}
export default nextConfig
`,
'next.config.mjs': js`export default {}`,
'app/layout.js': js`
import './globals.css'
Expand All @@ -117,12 +122,19 @@ describe.each(['turbo', 'webpack'])('%s', (bundler) => {
}
`,
'app/page.js': js`
import styles from './page.module.css'
export default function Page() {
return <h1 className="underline">Hello, Next.js!</h1>
return <h1 className={styles.heading + ' underline'}>Hello, Next.js!</h1>
}
`,
'app/page.module.css': css`
@reference './globals.css';
.heading {
@apply text-red-500 animate-ping content-['module'];
}
`,
'app/globals.css': css`
@import 'tailwindcss/theme' theme(reference);
@reference 'tailwindcss/theme';
@import 'tailwindcss/utilities';
`,
},
Expand All @@ -142,13 +154,16 @@ describe.each(['turbo', 'webpack'])('%s', (bundler) => {
await retryAssertion(async () => {
let css = await fetchStyles(url)
expect(css).toContain(candidate`underline`)
expect(css).toContain('content: var(--tw-content)')
expect(css).toContain('@keyframes')
})

await fs.write(
'app/page.js',
js`
import styles from './page.module.css'
export default function Page() {
return <h1 className="underline text-red-500">Hello, Next.js!</h1>
return <h1 className={styles.heading + ' underline bg-red-500'}>Hello, Next.js!</h1>
}
`,
)
Expand All @@ -157,7 +172,9 @@ describe.each(['turbo', 'webpack'])('%s', (bundler) => {
await retryAssertion(async () => {
let css = await fetchStyles(url)
expect(css).toContain(candidate`underline`)
expect(css).toContain(candidate`text-red-500`)
expect(css).toContain(candidate`bg-red-500`)
expect(css).toContain('content: var(--tw-content)')
expect(css).toContain('@keyframes')
})
},
)
Expand All @@ -181,21 +198,13 @@ test(
}
`,
'postcss.config.mjs': js`
/** @type {import('postcss-load-config').Config} */
const config = {
export default {
plugins: {
'@tailwindcss/postcss': {},
},
}
export default config
`,
'next.config.mjs': js`
/** @type {import('next').NextConfig} */
const nextConfig = {}
export default nextConfig
`,
'next.config.mjs': js`export default {}`,
'app/a/[slug]/page.js': js`
export default function Page() {
return <h1 className="content-['[slug]']">Hello, Next.js!</h1>
Expand Down
4 changes: 2 additions & 2 deletions integrations/postcss/plugins.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ test(
<div className="ui-open:flex"></div>
`,
'src/index.css': css`
@import 'tailwindcss/theme' theme(reference);
@reference 'tailwindcss/theme';
@import 'tailwindcss/utilities';
@plugin '@headlessui/tailwindcss';
`,
Expand Down Expand Up @@ -65,7 +65,7 @@ test(
<div className="ui-open:flex"></div>
`,
'src/index.css': css`
@import 'tailwindcss/theme' theme(reference);
@reference 'tailwindcss/theme';
@import 'tailwindcss/utilities';
@plugin '@headlessui/tailwindcss';
`,
Expand Down
6 changes: 3 additions & 3 deletions integrations/vite/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => {
}
`,
'project-a/src/index.css': css`
@import 'tailwindcss/theme' theme(reference);
@reference 'tailwindcss/theme';
@import 'tailwindcss/utilities';
@config '../tailwind.config.js';
@source '../../project-b/src/**/*.html';
Expand Down Expand Up @@ -147,7 +147,7 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => {
}
`,
'project-a/src/index.css': css`
@import 'tailwindcss/theme' theme(reference);
@reference 'tailwindcss/theme';
@import 'tailwindcss/utilities';
@config '../tailwind.config.js';
@source '../../project-b/src/**/*.html';
Expand Down Expand Up @@ -291,7 +291,7 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => {
}
`,
'project-a/src/index.css': css`
@import 'tailwindcss/theme' theme(reference);
@reference 'tailwindcss/theme';
@import 'tailwindcss/utilities';
@import './custom-theme.css';
@config '../tailwind.config.js';
Expand Down
4 changes: 2 additions & 2 deletions integrations/vite/multi-root.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ test(
</body>
`,
'src/shared.css': css`
@import 'tailwindcss/theme' theme(reference);
@reference 'tailwindcss/theme';
@import 'tailwindcss/utilities';
`,
'src/root1.css': css`
Expand Down Expand Up @@ -119,7 +119,7 @@ test(
</body>
`,
'src/shared.css': css`
@import 'tailwindcss/theme' theme(reference);
@reference 'tailwindcss/theme';
@import 'tailwindcss/utilities';
`,
'src/root1.css': css`
Expand Down
6 changes: 3 additions & 3 deletions integrations/vite/other-transforms.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ function createSetup(transformer: 'postcss' | 'lightningcss') {
</body>
`,
'src/index.css': css`
@import 'tailwindcss/theme' theme(reference);
@reference 'tailwindcss/theme';
@import 'tailwindcss/utilities';
.foo {
Expand Down Expand Up @@ -111,7 +111,7 @@ describe.each(['postcss', 'lightningcss'] as const)('%s', (transformer) => {
await fs.write(
'src/index.css',
css`
@import 'tailwindcss/theme' theme(reference);
@reference 'tailwindcss/theme';
@import 'tailwindcss/utilities';
.foo {
Expand Down Expand Up @@ -155,7 +155,7 @@ describe.each(['postcss', 'lightningcss'] as const)('%s', (transformer) => {
await fs.write(
'src/index.css',
css`
@import 'tailwindcss/theme' theme(reference);
@reference 'tailwindcss/theme';
@import 'tailwindcss/utilities';
.foo {
Expand Down
4 changes: 2 additions & 2 deletions integrations/vite/resolvers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => {
@plugin '#js-alias';
`,
'src/alias.css': css`
@import 'tailwindcss/theme' theme(reference);
@reference 'tailwindcss/theme';
@import 'tailwindcss/utilities';
`,
'src/plugin.js': js`
Expand Down Expand Up @@ -117,7 +117,7 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => {
@plugin '#js-alias';
`,
'src/alias.css': css`
@import 'tailwindcss/theme' theme(reference);
@reference 'tailwindcss/theme';
@import 'tailwindcss/utilities';
`,
'src/plugin.js': js`
Expand Down
Loading

0 comments on commit b389483

Please sign in to comment.