Skip to content

Commit f2ebb8e

Browse files
authored
Fix var(…) as the opacity value inside the theme(…) function (#14653)
Inside the `theme(…)` function, we can use the `/` character for applying an opacity. For example `theme(colors.red.500 / 50%)` will apply a 50% opacity to the `colors.red.500` value. However, if you used a variable instead of the hardcoded `50%` value, then this was not parsed correctly. E.g.: `theme(colors.red.500 / var(--opacity))` _If_ we have this exact syntax (with the spaces), then it parses, but some information is lost: ```html <div class="bg-[theme(colors.red.500_/_var(--opacity))]"></div> ``` Results in: ```css .bg-\[theme\(colors\.red\.500_\/_var\(--opacity\)\)\] { background-color: color-mix(in srgb, #ef4444 calc(var * 100%), transparent); } ``` Notice that the `var(--opacity)` is just parsed as `var`, and the `--opacity` is lost. Additionally, if we drop the spaces, then it doesn't parse at all: ```html <div class="bg-[theme(colors.red.500/var(--opacity))]"></div> ``` Results in: ```css ``` This means that we have to handle 2 issues to make this work: 1. We have to properly handle the `/` character as a proper separator. 2. If we have sub-functions, we have to make sure to print them in full (instead of only the very first node (`var` in this case)).
1 parent a64e209 commit f2ebb8e

File tree

5 files changed

+82
-1
lines changed

5 files changed

+82
-1
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3333
- Pass options when using `addComponents` and `matchComponents` ([#14590](https://github.com/tailwindlabs/tailwindcss/pull/14590))
3434
- Ensure `boxShadow` and `animation` theme keys in JS config files are accessible under `--shadow-*` and `--animate-*` using the `theme()` function ([#14642](https://github.com/tailwindlabs/tailwindcss/pull/14642))
3535
- Ensure all theme keys with new names are also accessible under their old names when using the `theme()` function with the legacy dot notation syntax ([#14642](https://github.com/tailwindlabs/tailwindcss/pull/14642))
36+
- Ensure `var(…)` can be used as the opacity value inside the `theme([path] / [modifier])` function ([#14653](https://github.com/tailwindlabs/tailwindcss/pull/14653))
3637
- _Upgrade (experimental)_: Ensure CSS before a layer stays unlayered when running codemods ([#14596](https://github.com/tailwindlabs/tailwindcss/pull/14596))
3738
- _Upgrade (experimental)_: Resolve issues where some prefixed candidates were not properly migrated ([#14600](https://github.com/tailwindlabs/tailwindcss/pull/14600))
3839

packages/tailwindcss/src/css-functions.test.ts

+64
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,27 @@ describe('theme function', () => {
136136
`)
137137
})
138138

139+
test('theme(colors.red.500/75%)', async () => {
140+
expect(
141+
await compileCss(css`
142+
@theme {
143+
--color-red-500: #f00;
144+
}
145+
.red {
146+
color: theme(colors.red.500/75%);
147+
}
148+
`),
149+
).toMatchInlineSnapshot(`
150+
":root {
151+
--color-red-500: red;
152+
}
153+
154+
.red {
155+
color: #ff0000bf;
156+
}"
157+
`)
158+
})
159+
139160
test('theme(colors.red.500 / 75%)', async () => {
140161
expect(
141162
await compileCss(css`
@@ -178,6 +199,49 @@ describe('theme function', () => {
178199
`)
179200
})
180201

202+
test('theme(colors.red.500/var(--opacity))', async () => {
203+
expect(
204+
await compileCss(css`
205+
@theme {
206+
--color-red-500: #f00;
207+
}
208+
.red {
209+
color: theme(colors.red.500/var(--opacity));
210+
}
211+
`),
212+
).toMatchInlineSnapshot(`
213+
":root {
214+
--color-red-500: red;
215+
}
216+
217+
.red {
218+
color: color-mix(in srgb, red calc(var(--opacity) * 100%), transparent);
219+
}"
220+
`)
221+
})
222+
223+
test('theme(colors.red.500/var(--opacity,50%))', async () => {
224+
expect(
225+
await compileCss(css`
226+
@theme {
227+
--color-red-500: #f00;
228+
}
229+
.red {
230+
/* prettier-ignore */
231+
color: theme(colors.red.500/var(--opacity,50%));
232+
}
233+
`),
234+
).toMatchInlineSnapshot(`
235+
":root {
236+
--color-red-500: red;
237+
}
238+
239+
.red {
240+
color: color-mix(in srgb, red calc(var(--opacity, 50%) * 100%), transparent);
241+
}"
242+
`)
243+
})
244+
181245
test('theme(spacing.12)', async () => {
182246
expect(
183247
await compileCss(css`

packages/tailwindcss/src/css-functions.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export function substituteFunctionsInValue(
6868
if (node.nodes[i].value.includes(',')) {
6969
break
7070
}
71-
path += node.nodes[i].value
71+
path += ValueParser.toCss([node.nodes[i]])
7272
skipUntilIndex = i + 1
7373
}
7474

packages/tailwindcss/src/value-parser.test.ts

+14
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,20 @@ describe('parse', () => {
6666
])
6767
})
6868

69+
it('should parse a function with nested arguments separated by `/`', () => {
70+
expect(parse('theme(colors.red.500/var(--opacity))')).toEqual([
71+
{
72+
kind: 'function',
73+
value: 'theme',
74+
nodes: [
75+
{ kind: 'word', value: 'colors.red.500' },
76+
{ kind: 'separator', value: '/' },
77+
{ kind: 'function', value: 'var', nodes: [{ kind: 'word', value: '--opacity' }] },
78+
],
79+
},
80+
])
81+
})
82+
6983
it('should handle calculations', () => {
7084
expect(parse('calc((1 + 2) * 3)')).toEqual([
7185
{

packages/tailwindcss/src/value-parser.ts

+2
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ const SPACE = 0x20
114114
const LESS_THAN = 0x3c
115115
const GREATER_THAN = 0x3e
116116
const EQUALS = 0x3d
117+
const SLASH = 0x2f
117118

118119
export function parse(input: string) {
119120
input = input.replaceAll('\r\n', '\n')
@@ -143,6 +144,7 @@ export function parse(input: string) {
143144
case COLON:
144145
case COMMA:
145146
case SPACE:
147+
case SLASH:
146148
case LESS_THAN:
147149
case GREATER_THAN:
148150
case EQUALS: {

0 commit comments

Comments
 (0)