Skip to content

Commit 3e9cf87

Browse files
Make polyfill work when the theme variable resolves to another var (#17562)
Discovered while triaging #17556 This PR improves the `color-mix(...)` polyfill to ensure it works when a theme key links to another theme key via a `var(...)` call. Imagine this setup: ```css @theme { --color-red: var(--color-red-500); --color-red-500: oklch(63.7% 0.237 25.331); } @source inline("text-red/50"); ```` Since `--color-red` will link to `--color-red-500` _which is also a known theme variable_, we can inline the value of `--color-red-500` into the fallback now: ```css .text-red\\/50 { color: var(--color-red); } @supports (color: color-mix(in lab, red, red)) { .text-red\\/50 { color: color-mix(in oklab, var(--color-red) 50%, transparent); } } ``` This will allow for slightly less confusing behavior. The code added also handles recursive definitions where a color is linking to another color that is again linking to the first one (by adding a `Set` to keep track of already seen variable names). ## Test plan - Added unit test
1 parent 811e97d commit 3e9cf87

File tree

3 files changed

+99
-9
lines changed

3 files changed

+99
-9
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Fixed
1111

1212
- Ensure `color-mix(…)` polyfills do not cause used CSS variables to be removed ([#17555](https://github.com/tailwindlabs/tailwindcss/pull/17555))
13+
- Ensure the `color-mix(…)` polyfill creates fallbacks for theme variables that reference other theme variables ([#17562](https://github.com/tailwindlabs/tailwindcss/pull/17562))
1314

1415
## [4.1.3] - 2025-04-04
1516

packages/tailwindcss/src/ast.ts

+40-9
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,7 @@ export function optimizeAst(
542542
let requiresPolyfill = false
543543
ValueParser.walk(ast, (node, { replaceWith }) => {
544544
if (node.kind !== 'function' || node.value !== 'color-mix') return
545+
545546
let containsUnresolvableVars = false
546547
let containsCurrentcolor = false
547548
ValueParser.walk(node.nodes, (node, { replaceWith }) => {
@@ -550,17 +551,47 @@ export function optimizeAst(
550551
requiresPolyfill = true
551552
return
552553
}
553-
if (node.kind !== 'function' || node.value !== 'var') return
554-
let firstChild = node.nodes[0]
555-
if (!firstChild || firstChild.kind !== 'word') return
556-
requiresPolyfill = true
557-
let inlinedColor = designSystem.theme.resolveValue(null, [firstChild.value as any])
558-
if (!inlinedColor) {
559-
containsUnresolvableVars = true
560-
return
561-
}
554+
555+
let varNode: ValueParser.ValueAstNode | null = node
556+
let inlinedColor: string | null = null
557+
let seenVariables = new Set<string>()
558+
do {
559+
if (varNode.kind !== 'function' || varNode.value !== 'var') return
560+
let firstChild = varNode.nodes[0]
561+
if (!firstChild || firstChild.kind !== 'word') return
562+
563+
let variableName = firstChild.value
564+
565+
if (seenVariables.has(variableName)) {
566+
containsUnresolvableVars = true
567+
return
568+
}
569+
570+
seenVariables.add(variableName)
571+
572+
requiresPolyfill = true
573+
574+
inlinedColor = designSystem.theme.resolveValue(null, [firstChild.value as any])
575+
if (!inlinedColor) {
576+
containsUnresolvableVars = true
577+
return
578+
}
579+
if (inlinedColor.toLowerCase() === 'currentcolor') {
580+
containsCurrentcolor = true
581+
return
582+
}
583+
584+
if (inlinedColor.startsWith('var(')) {
585+
let subAst = ValueParser.parse(inlinedColor)
586+
varNode = subAst[0]
587+
} else {
588+
varNode = null
589+
}
590+
} while (varNode)
591+
562592
replaceWith({ kind: 'word', value: inlinedColor })
563593
})
594+
564595
if (containsUnresolvableVars || containsCurrentcolor) {
565596
let separatorIndex = node.nodes.findIndex(
566597
(node) => node.kind === 'separator' && node.value.trim().includes(','),

packages/tailwindcss/src/index.test.ts

+58
Original file line numberDiff line numberDiff line change
@@ -4764,6 +4764,36 @@ describe('`color-mix(…)` polyfill', () => {
47644764
`)
47654765
})
47664766

4767+
it('creates an inlined variable version of the color-mix(…) usages when it resolves to a var(…) containing another theme variable', async () => {
4768+
await expect(
4769+
compileCss(
4770+
css`
4771+
@theme {
4772+
--color-red: var(--color-red-500);
4773+
--color-red-500: oklch(63.7% 0.237 25.331);
4774+
}
4775+
@tailwind utilities;
4776+
`,
4777+
['text-red/50'],
4778+
),
4779+
).resolves.toMatchInlineSnapshot(`
4780+
":root, :host {
4781+
--color-red: var(--color-red-500);
4782+
--color-red-500: oklch(63.7% .237 25.331);
4783+
}
4784+
4785+
.text-red\\/50 {
4786+
color: #fb2c3680;
4787+
}
4788+
4789+
@supports (color: color-mix(in lab, red, red)) {
4790+
.text-red\\/50 {
4791+
color: color-mix(in oklab, var(--color-red) 50%, transparent);
4792+
}
4793+
}"
4794+
`)
4795+
})
4796+
47674797
it('works for color values in the first and second position', async () => {
47684798
await expect(
47694799
compileCss(
@@ -4971,6 +5001,34 @@ describe('`color-mix(…)` polyfill', () => {
49715001
`)
49725002
})
49735003

5004+
it('uses the first color value as the fallback when the `color-mix(…)` function contains theme variables that resolves to other variables', async () => {
5005+
await expect(
5006+
compileCss(
5007+
css`
5008+
@tailwind utilities;
5009+
@theme {
5010+
--color-red: var(--my-red);
5011+
}
5012+
`,
5013+
['text-red/50'],
5014+
),
5015+
).resolves.toMatchInlineSnapshot(`
5016+
".text-red\\/50 {
5017+
color: var(--color-red);
5018+
}
5019+
5020+
@supports (color: color-mix(in lab, red, red)) {
5021+
.text-red\\/50 {
5022+
color: color-mix(in oklab, var(--color-red) 50%, transparent);
5023+
}
5024+
}
5025+
5026+
:root, :host {
5027+
--color-red: var(--my-red);
5028+
}"
5029+
`)
5030+
})
5031+
49745032
it('uses the first color value of the inner most `color-mix(…)` function as the fallback when nested `color-mix(…)` function all contain non-theme variables', async () => {
49755033
await expect(
49765034
compileCss(

0 commit comments

Comments
 (0)