Skip to content

Commit

Permalink
Allow theme(…) options when using @import (#16514)
Browse files Browse the repository at this point in the history
This PR improves the developer experience when trying to use `theme(…)`
options on an import.

Today, if you want to use Tailwind CSS, you can import it as:

```css
@import "tailwindcss";
```

But if you want to use any of the `theme(…)` options, like the `static`
theme option, then you had to use this instead:

```css
@layer theme, base, components, utilities;

@import 'tailwindcss/theme' layer(theme) theme(static);
@import 'tailwindcss/preflight' layer(base);
@import 'tailwindcss/utilities' layer(utilities);
```
In this scenario you have to be careful, because the `layer(…)` _must_
be the first option after an import (according to the spec). So if you
use `@import 'tailwindcss/theme' theme(static) layer(theme);` then
that's not going to work either.

This PR solves that by allowing you to use the `theme(…)` options
directly on the `@import` statement:

```css
@import 'tailwindcss' theme(static);
```

The only edge case is when you want to use `theme(reference)`. A typical
use case is for projects with `<style>` blocks where you want to
_reference_ the CSS variables from the theme.

If you use `@import 'tailwindcss' theme(reference);`, then all `@theme`
blocks will be references and you can reference theme values. This is
good. The bad part is that `@import 'tailwindcss';` also includes
preflight CSS. This means that we will import the preflight CSS for
every `<style>` block. This is probably not what you want.

The solution is to use `@reference 'tailwindcss';` instead which strips
all of that information and only gives you access to CSS variables.

This PR also makes sure that if you use `theme(reference)` on an import
that we still throw an error and suggest you use `@reference` instead.
This is not a breaking change because right now if you use `@import`
with `theme(…)` options it will already throw an error.

### Test plan:

1. Added dedicated tests to make sure we don't throw anymore.
2. Added test to make sure that we _do_ throw when using
`theme(reference)` on an import.
  • Loading branch information
RobinMalfait authored Feb 20, 2025
1 parent 88b762b commit 3f270d2
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Vite: Ensure setups with multiple Vite builds work as expected ([#16631](https://github.com/tailwindlabs/tailwindcss/pull/16631))
- Vite: Ensure Astro production builds contain classes for client-only components ([#16631](https://github.com/tailwindlabs/tailwindcss/pull/16631))
- Vite: Ensure utility classes are read without escaping special characters ([#16631](https://github.com/tailwindlabs/tailwindcss/pull/16631))
- Allow `theme(…)` options when using `@import` ([#16514](https://github.com/tailwindlabs/tailwindcss/pull/16514))

## [4.0.7] - 2025-02-18

Expand Down
84 changes: 80 additions & 4 deletions packages/tailwindcss/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1021,8 +1021,7 @@ describe('sorting', () => {
})
})

// Parsing theme values from CSS
describe('Parsing themes values from CSS', () => {
describe('Parsing theme values from CSS', () => {
test('Can read values from `@theme`', async () => {
expect(
await compileCss(
Expand Down Expand Up @@ -2020,7 +2019,44 @@ describe('Parsing themes values from CSS', () => {
`)
})

test('`@media theme(…)` can only contain `@theme` rules', () => {
test('`@import "tailwindcss" theme(static)` will always generate theme values, regardless of whether they were used or not', async () => {
expect(
await compileCss(
css`
@import 'tailwindcss' theme(static);
`,
['bg-tomato'],
{
async loadStylesheet() {
return {
content: css`
@theme {
--color-tomato: #e10c04;
--color-potato: #ac855b;
--color-primary: var(--primary);
}
@tailwind utilities;
`,
base: '',
}
},
},
),
).toMatchInlineSnapshot(`
":root, :host {
--color-tomato: #e10c04;
--color-potato: #ac855b;
--color-primary: var(--primary);
}
.bg-tomato {
background-color: var(--color-tomato);
}"
`)
})

test('`@media theme(reference)` can only contain `@theme` rules', () => {
return expect(
compileCss(
css`
Expand All @@ -2034,7 +2070,10 @@ describe('Parsing themes values from CSS', () => {
['bg-tomato', 'bg-potato', 'bg-avocado'],
),
).rejects.toThrowErrorMatchingInlineSnapshot(
`[Error: Files imported with \`@import "…" theme(…)\` must only contain \`@theme\` blocks.]`,
`
[Error: Files imported with \`@import "…" theme(reference)\` must only contain \`@theme\` blocks.
Use \`@reference "…";\` instead.]
`,
)
})

Expand Down Expand Up @@ -2073,6 +2112,43 @@ describe('Parsing themes values from CSS', () => {
`)
})

test('`@import "tailwindcss" theme(inline)` theme values added as `inline` are not wrapped in `var(…)` when used as utility values', async () => {
expect(
await compileCss(
css`
@import 'tailwindcss' theme(inline);
`,
['bg-tomato'],
{
async loadStylesheet() {
return {
content: css`
@theme {
--color-tomato: #e10c04;
--color-potato: #ac855b;
--color-primary: var(--primary);
}
@tailwind utilities;
`,
base: '',
}
},
},
),
).toMatchInlineSnapshot(`
":root, :host {
--color-tomato: #e10c04;
--color-potato: #ac855b;
--color-primary: var(--primary);
}
.bg-tomato {
background-color: #e10c04;
}"
`)
})

test('theme values added as `static` will always be generated, regardless of whether they were used or not', async () => {
expect(
await compileCss(
Expand Down
14 changes: 10 additions & 4 deletions packages/tailwindcss/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,18 +380,24 @@ async function parseCss(

// Handle `@media theme(…)`
//
// We support `@import "tailwindcss/theme" theme(reference)` as a way to
// We support `@import "tailwindcss" theme(reference)` as a way to
// import an external theme file as a reference, which becomes `@media
// theme(reference) { … }` when the `@import` is processed.
else if (param.startsWith('theme(')) {
let themeParams = param.slice(6, -1)
let hasReference = themeParams.includes('reference')

walk(node.nodes, (child) => {
if (child.kind !== 'at-rule') {
throw new Error(
'Files imported with `@import "…" theme(…)` must only contain `@theme` blocks.',
)
if (hasReference) {
throw new Error(
`Files imported with \`@import "…" theme(reference)\` must only contain \`@theme\` blocks.\nUse \`@reference "…";\` instead.`,
)
}

return WalkAction.Continue
}

if (child.name === '@theme') {
child.params += ' ' + themeParams
return WalkAction.Skip
Expand Down
6 changes: 5 additions & 1 deletion packages/tailwindcss/src/test-utils/run.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { Features, transform } from 'lightningcss'
import { compile } from '..'

export async function compileCss(css: string, candidates: string[] = [], options = {}) {
export async function compileCss(
css: string,
candidates: string[] = [],
options: Parameters<typeof compile>[1] = {},
) {
let { build } = await compile(css, options)
return optimizeCss(build(candidates)).trim()
}
Expand Down

0 comments on commit 3f270d2

Please sign in to comment.