Skip to content

Commit ca7b10e

Browse files
Make --theme(…) return CSS variables (#17036)
Closes #16945 This PR changes the `--theme(…)` function now return CSS `var(…)` definitions unless used in places where `var(…)` is not valid CSS (e.g. in `@media (width >= theme(--breakpoint-md))`): ```css /* input */ @theme { --color-red: red; } .red { color: --theme(--color-red); } /* output */ :root, :host { --color-red: red; } .red { color: var(--color-red); } ``` Furthermore, this adds an `--theme(… inline)` option to the `--theme(…)` function to force the resolution to be inline, e.g.: ```css /* input */ @theme { --color-red: red; } .red { color: --theme(--color-red inline); } /* output */ .red { color: red; } ``` This PR also changes preflight and the default theme to use this new `--theme(…)` function to ensure variables are prefixed correctly. ## Test plan - Added unit tests and a test that pulls in the whole preflight under a prefix theme.
1 parent a1acaee commit ca7b10e

File tree

14 files changed

+635
-44
lines changed

14 files changed

+635
-44
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2626
- Fix `border-[12px_4px]` being interpreted as a `border-color` instead of a `border-width` ([#17248](https://github.com/tailwindlabs/tailwindcss/pull/17248))
2727
- Use the `oklab(…)` function when applying opacity to `currentColor` to work around a crash in Safari 16.4 and 16.5 ([#17247](https://github.com/tailwindlabs/tailwindcss/pull/17247))
2828
- Pre-process `<template lang="…">` in Vue files ([#17252](https://github.com/tailwindlabs/tailwindcss/pull/17252))
29+
- Ensure that all CSS variables used by preflight are prefixed ([#17036](https://github.com/tailwindlabs/tailwindcss/pull/17036))
30+
31+
### Changed
32+
33+
- The `--theme(…)` function now returns CSS variables from your theme variables unless used inside positions where CSS variables are invalid (e.g. inside `@media` queries) ([#17036](https://github.com/tailwindlabs/tailwindcss/pull/17036))
2934
- Remove redundant `line-height: initial` from Preflight ([#15212](https://github.com/tailwindlabs/tailwindcss/pull/15212))
3035
- Prevent segfault when loaded in a worker thread on Linux ([#17276](https://github.com/tailwindlabs/tailwindcss/pull/17276))
3136
- Ensure multiple `--value(…)` or `--modifier(…)` calls don't delete subsequent declarations ([#17273](https://github.com/tailwindlabs/tailwindcss/pull/17273))

packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap

-4
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,7 @@ exports[`\`@import 'tailwindcss'\` is replaced with the generated CSS 1`] = `
1010
--text-2xl--line-height: calc(2 / 1.5);
1111
--font-weight-bold: 700;
1212
--default-font-family: var(--font-sans);
13-
--default-font-feature-settings: var(--font-sans--font-feature-settings);
14-
--default-font-variation-settings: var(--font-sans--font-variation-settings);
1513
--default-mono-font-family: var(--font-mono);
16-
--default-mono-font-feature-settings: var(--font-mono--font-feature-settings);
17-
--default-mono-font-variation-settings: var(--font-mono--font-variation-settings);
1814
}
1915
}
2016

packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export function migrateMediaScreen({
1919
let screens = resolvedConfig?.theme?.screens || {}
2020

2121
let mediaQueries = new DefaultMap<string, string | null>((name) => {
22-
let value = designSystem?.resolveThemeValue(`--breakpoint-${name}`) ?? screens?.[name]
22+
let value = designSystem?.resolveThemeValue(`--breakpoint-${name}`, true) ?? screens?.[name]
2323
if (typeof value === 'string') return `(width >= theme(--breakpoint-${name}))`
2424
return value ? buildMediaQuery(value) : null
2525
})

packages/@tailwindcss-upgrade/src/template/codemods/legacy-classes.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,9 @@ export async function legacyClasses(
139139

140140
if (fromThemeKey && toThemeKey) {
141141
// Migrating something that resolves to a value in the theme.
142-
let customFrom = designSystem.resolveThemeValue(fromThemeKey)
143-
let defaultFrom = defaultDesignSystem.resolveThemeValue(fromThemeKey)
144-
let customTo = designSystem.resolveThemeValue(toThemeKey)
142+
let customFrom = designSystem.resolveThemeValue(fromThemeKey, true)
143+
let defaultFrom = defaultDesignSystem.resolveThemeValue(fromThemeKey, true)
144+
let customTo = designSystem.resolveThemeValue(toThemeKey, true)
145145
let defaultTo = defaultDesignSystem.resolveThemeValue(toThemeKey)
146146

147147
// The new theme value is not defined, which means we can't safely

packages/tailwindcss/preflight.css

+6-6
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ html,
3030
line-height: 1.5; /* 1 */
3131
-webkit-text-size-adjust: 100%; /* 2 */
3232
tab-size: 4; /* 3 */
33-
font-family: var(
33+
font-family: --theme(
3434
--default-font-family,
3535
ui-sans-serif,
3636
system-ui,
@@ -40,8 +40,8 @@ html,
4040
'Segoe UI Symbol',
4141
'Noto Color Emoji'
4242
); /* 4 */
43-
font-feature-settings: var(--default-font-feature-settings, normal); /* 5 */
44-
font-variation-settings: var(--default-font-variation-settings, normal); /* 6 */
43+
font-feature-settings: --theme(--default-font-feature-settings, normal); /* 5 */
44+
font-variation-settings: --theme(--default-font-variation-settings, normal); /* 6 */
4545
-webkit-tap-highlight-color: transparent; /* 7 */
4646
}
4747

@@ -110,7 +110,7 @@ code,
110110
kbd,
111111
samp,
112112
pre {
113-
font-family: var(
113+
font-family: --theme(
114114
--default-mono-font-family,
115115
ui-monospace,
116116
SFMono-Regular,
@@ -121,8 +121,8 @@ pre {
121121
'Courier New',
122122
monospace
123123
); /* 1 */
124-
font-feature-settings: var(--default-mono-font-feature-settings, normal); /* 2 */
125-
font-variation-settings: var(--default-mono-font-variation-settings, normal); /* 3 */
124+
font-feature-settings: --theme(--default-mono-font-feature-settings, normal); /* 2 */
125+
font-variation-settings: --theme(--default-mono-font-variation-settings, normal); /* 3 */
126126
font-size: 1em; /* 4 */
127127
}
128128

packages/tailwindcss/src/__snapshots__/index.test.ts.snap

+245
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,248 @@ exports[`compiling CSS > \`@tailwind utilities\` is replaced by utilities using
9292
initial-value: 0 0 #0000;
9393
}"
9494
`;
95+
96+
exports[`compiling CSS > prefix all CSS variables inside preflight 1`] = `
97+
"@layer theme {
98+
:root, :host {
99+
--tw-font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
100+
--tw-font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
101+
--tw-default-font-family: var(--tw-font-sans);
102+
--tw-default-mono-font-family: var(--tw-font-mono);
103+
}
104+
}
105+
106+
@layer base {
107+
*, :after, :before, ::backdrop {
108+
box-sizing: border-box;
109+
border: 0 solid;
110+
margin: 0;
111+
padding: 0;
112+
}
113+
114+
::file-selector-button {
115+
box-sizing: border-box;
116+
border: 0 solid;
117+
margin: 0;
118+
padding: 0;
119+
}
120+
121+
html, :host {
122+
-webkit-text-size-adjust: 100%;
123+
tab-size: 4;
124+
line-height: 1.5;
125+
font-family: var(--tw-default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");
126+
font-feature-settings: var(--tw-default-font-feature-settings, normal);
127+
font-variation-settings: var(--tw-default-font-variation-settings, normal);
128+
-webkit-tap-highlight-color: transparent;
129+
}
130+
131+
hr {
132+
height: 0;
133+
color: inherit;
134+
border-top-width: 1px;
135+
}
136+
137+
abbr:where([title]) {
138+
-webkit-text-decoration: underline dotted;
139+
text-decoration: underline dotted;
140+
}
141+
142+
h1, h2, h3, h4, h5, h6 {
143+
font-size: inherit;
144+
font-weight: inherit;
145+
}
146+
147+
a {
148+
color: inherit;
149+
-webkit-text-decoration: inherit;
150+
-webkit-text-decoration: inherit;
151+
-webkit-text-decoration: inherit;
152+
text-decoration: inherit;
153+
}
154+
155+
b, strong {
156+
font-weight: bolder;
157+
}
158+
159+
code, kbd, samp, pre {
160+
font-family: var(--tw-default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);
161+
font-feature-settings: var(--tw-default-mono-font-feature-settings, normal);
162+
font-variation-settings: var(--tw-default-mono-font-variation-settings, normal);
163+
font-size: 1em;
164+
}
165+
166+
small {
167+
font-size: 80%;
168+
}
169+
170+
sub, sup {
171+
vertical-align: baseline;
172+
font-size: 75%;
173+
line-height: 0;
174+
position: relative;
175+
}
176+
177+
sub {
178+
bottom: -.25em;
179+
}
180+
181+
sup {
182+
top: -.5em;
183+
}
184+
185+
table {
186+
text-indent: 0;
187+
border-color: inherit;
188+
border-collapse: collapse;
189+
}
190+
191+
:-moz-focusring {
192+
outline: auto;
193+
}
194+
195+
progress {
196+
vertical-align: baseline;
197+
}
198+
199+
summary {
200+
display: list-item;
201+
}
202+
203+
ol, ul, menu {
204+
list-style: none;
205+
}
206+
207+
img, svg, video, canvas, audio, iframe, embed, object {
208+
vertical-align: middle;
209+
display: block;
210+
}
211+
212+
img, video {
213+
max-width: 100%;
214+
height: auto;
215+
}
216+
217+
button, input, select, optgroup, textarea {
218+
font: inherit;
219+
font-feature-settings: inherit;
220+
font-variation-settings: inherit;
221+
letter-spacing: inherit;
222+
color: inherit;
223+
opacity: 1;
224+
background-color: #0000;
225+
border-radius: 0;
226+
}
227+
228+
::file-selector-button {
229+
font: inherit;
230+
font-feature-settings: inherit;
231+
font-variation-settings: inherit;
232+
letter-spacing: inherit;
233+
color: inherit;
234+
opacity: 1;
235+
background-color: #0000;
236+
border-radius: 0;
237+
}
238+
239+
:where(select:is([multiple], [size])) optgroup {
240+
font-weight: bolder;
241+
}
242+
243+
:where(select:is([multiple], [size])) optgroup option {
244+
padding-inline-start: 20px;
245+
}
246+
247+
::file-selector-button {
248+
margin-inline-end: 4px;
249+
}
250+
251+
::placeholder {
252+
opacity: 1;
253+
color: oklab(from currentColor l a b / 50%);
254+
}
255+
256+
textarea {
257+
resize: vertical;
258+
}
259+
260+
::-webkit-search-decoration {
261+
-webkit-appearance: none;
262+
}
263+
264+
::-webkit-date-and-time-value {
265+
min-height: 1lh;
266+
text-align: inherit;
267+
}
268+
269+
::-webkit-datetime-edit {
270+
display: inline-flex;
271+
}
272+
273+
::-webkit-datetime-edit-fields-wrapper {
274+
padding: 0;
275+
}
276+
277+
::-webkit-datetime-edit {
278+
padding-block: 0;
279+
}
280+
281+
::-webkit-datetime-edit-year-field {
282+
padding-block: 0;
283+
}
284+
285+
::-webkit-datetime-edit-month-field {
286+
padding-block: 0;
287+
}
288+
289+
::-webkit-datetime-edit-day-field {
290+
padding-block: 0;
291+
}
292+
293+
::-webkit-datetime-edit-hour-field {
294+
padding-block: 0;
295+
}
296+
297+
::-webkit-datetime-edit-minute-field {
298+
padding-block: 0;
299+
}
300+
301+
::-webkit-datetime-edit-second-field {
302+
padding-block: 0;
303+
}
304+
305+
::-webkit-datetime-edit-millisecond-field {
306+
padding-block: 0;
307+
}
308+
309+
::-webkit-datetime-edit-meridiem-field {
310+
padding-block: 0;
311+
}
312+
313+
:-moz-ui-invalid {
314+
box-shadow: none;
315+
}
316+
317+
button, input:where([type="button"], [type="reset"], [type="submit"]) {
318+
appearance: button;
319+
}
320+
321+
::file-selector-button {
322+
appearance: button;
323+
}
324+
325+
::-webkit-inner-spin-button {
326+
height: auto;
327+
}
328+
329+
::-webkit-outer-spin-button {
330+
height: auto;
331+
}
332+
333+
[hidden]:where(:not([hidden="until-found"])) {
334+
display: none !important;
335+
}
336+
}
337+
338+
@layer components, utilities;"
339+
`;

packages/tailwindcss/src/ast.ts

+7
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,13 @@ export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) {
283283

284284
// Track variables defined in `@theme`
285285
if (context.theme && node.property[0] === '-' && node.property[1] === '-') {
286+
// Variables that resolve to `initial` should never be emitted. This can happen because of
287+
// the `--theme(…)` being used and evaluated lazily
288+
if (node.value === 'initial') {
289+
node.value = undefined
290+
return
291+
}
292+
286293
if (!context.keyframes) {
287294
cssThemeVariables.get(parent).add(node)
288295
}

packages/tailwindcss/src/compat/apply-compat-hooks.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,9 @@ export async function applyCompatibilityHooks({
134134
// compatibility concerns localized to our compatibility layer.
135135
let resolveThemeVariableValue = designSystem.resolveThemeValue
136136

137-
designSystem.resolveThemeValue = function resolveThemeValue(path: string) {
137+
designSystem.resolveThemeValue = function resolveThemeValue(path: string, forceInline?: boolean) {
138138
if (path.startsWith('--')) {
139-
return resolveThemeVariableValue(path)
139+
return resolveThemeVariableValue(path, forceInline)
140140
}
141141

142142
// If the theme value is not found in the simple resolver, we upgrade to the full backward
@@ -149,7 +149,7 @@ export async function applyCompatibilityHooks({
149149
configs: [],
150150
pluginDetails: [],
151151
})
152-
return designSystem.resolveThemeValue(path)
152+
return designSystem.resolveThemeValue(path, forceInline)
153153
}
154154

155155
// If there are no plugins or configs registered, we don't need to register
@@ -260,8 +260,8 @@ function upgradeToFullPluginSupport({
260260
// config files are actually being used. In the future we may want to optimize
261261
// this further by only doing this if plugins or config files _actually_
262262
// registered JS config objects.
263-
designSystem.resolveThemeValue = function resolveThemeValue(path: string, defaultValue?: string) {
264-
let resolvedValue = pluginApi.theme(path, defaultValue)
263+
designSystem.resolveThemeValue = function resolveThemeValue(path: string, forceInline?: boolean) {
264+
let resolvedValue = pluginApi.theme(path, forceInline)
265265

266266
if (Array.isArray(resolvedValue) && resolvedValue.length === 2) {
267267
// When a tuple is returned, return the first element

0 commit comments

Comments
 (0)