Polyfill: Fall back to first color value when color-mix(…) contains unresolvable var(…) (#17513)

This PR further improves the `color-mix(…)` polyfill to create a
reasonable fallback if dynamic values that can not statically be
resolved are used. This refers to either the use of `currentcolor` or
any variables that are not static theme variables.

Here are two examples that now generate a reasonable fallback instead of
not showing any color at all:

```css
.text-\\(--my-color\\)\\/\\(--my-opacity\\) {
  color: var(--my-color);
}
@supports (color: color-mix(in lab, red, red)) {
  .text-\\(--my-color\\)\\/\\(--my-opacity\\) {
    color: color-mix(in oklab, var(--my-color) var(--my-opacity), transparent);
  }
}
```

```css
.text-current\\/50 {
  color: currentColor;
}

@supports (color: color-mix(in lab, red, red)) {
  .text-current\\/50 {
    color: color-mix(in oklab, currentColor 50%, transparent);
  }
}
```

## Test plan

- Made sure the test diffs are looking reasonable
- Tested this on a production site with `<p className="text-shadow-lg/50
[--my-color:red] text-shadow-(color:--my-color)">shadow test</p>`
- Browsers that do not support `color-mix(…)` will properly show a red
shadow now albeit with 100% opacity: iOS 15.5 and Chrome 110
- Browsers that I have tested to make sure it still works there with
opacity: Firefox 127, Firefox 128, Latest Chrome, Safari, Firefox
- Browsers that do show a black shadow because of `var(…)var(…)` being
chained with no space by lightningcss: Chrome 111
This commit is contained in:
Philipp Spiess 2025-04-03 17:12:34 +02:00 committed by GitHub
parent 81a676f129
commit 60b0da90ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 1754 additions and 305 deletions

View File

@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix `drop-shadow-*` utilities that use multiple shadows in `@theme inline` ([#17515](https://github.com/tailwindlabs/tailwindcss/pull/17515))
- PostCSS: Fix race condition when two changes are queued concurrently ([#17514](https://github.com/tailwindlabs/tailwindcss/pull/17514))
- PostCSS: Ensure we process files containing an `@tailwind utilities;` directive ([#17514](https://github.com/tailwindlabs/tailwindcss/pull/17514))
- Ensure the `color-mix(…)` polyfill creates fallbacks even when using colors that can not be statically analyzed ([#17513](https://github.com/tailwindlabs/tailwindcss/pull/17513))
## [4.1.1] - 2025-04-02

View File

@ -1711,7 +1711,10 @@ test(
}
@supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {
::placeholder {
color: color-mix(in oklab, currentcolor 50%, transparent);
color: currentcolor;
@supports (color: color-mix(in lab, red, red)) {
color: color-mix(in oklab, currentcolor 50%, transparent);
}
}
}
textarea {

View File

@ -173,7 +173,13 @@ exports[`\`@import 'tailwindcss'\` is replaced with the generated CSS 1`] = `
@supports (not ((-webkit-appearance: -apple-pay-button))) or (contain-intrinsic-size: 1px) {
::placeholder {
color: color-mix(in oklab, currentcolor 50%, transparent);
color: currentColor;
}
@supports (color: color-mix(in lab, red, red)) {
::placeholder {
color: color-mix(in oklab, currentcolor 50%, transparent);
}
}
}

View File

@ -287,7 +287,13 @@ exports[`compiling CSS > prefix all CSS variables inside preflight 1`] = `
@supports (not ((-webkit-appearance: -apple-pay-button))) or (contain-intrinsic-size: 1px) {
::placeholder {
color: color-mix(in oklab, currentcolor 50%, transparent);
color: currentColor;
}
@supports (color: color-mix(in lab, red, red)) {
::placeholder {
color: color-mix(in oklab, currentcolor 50%, transparent);
}
}
}

View File

@ -91,28 +91,34 @@ exports[`border-* 1`] = `
border-color: oklab(59.9824% -.067 -.124 / .5);
}
.border-\\[color\\:var\\(--my-color\\)\\] {
.border-\\[color\\:var\\(--my-color\\)\\], .border-\\[color\\:var\\(--my-color\\)\\]\\/50 {
border-color: var(--my-color);
}
.border-\\[color\\:var\\(--my-color\\)\\]\\/50 {
border-color: color-mix(in oklab, var(--my-color) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.border-\\[color\\:var\\(--my-color\\)\\]\\/50 {
border-color: color-mix(in oklab, var(--my-color) 50%, transparent);
}
}
.border-\\[var\\(--my-color\\)\\] {
.border-\\[var\\(--my-color\\)\\], .border-\\[var\\(--my-color\\)\\]\\/50 {
border-color: var(--my-color);
}
.border-\\[var\\(--my-color\\)\\]\\/50 {
border-color: color-mix(in oklab, var(--my-color) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.border-\\[var\\(--my-color\\)\\]\\/50 {
border-color: color-mix(in oklab, var(--my-color) 50%, transparent);
}
}
.border-current {
.border-current, .border-current\\/50 {
border-color: currentColor;
}
.border-current\\/50 {
border-color: color-mix(in oklab, currentcolor 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.border-current\\/50 {
border-color: color-mix(in oklab, currentcolor 50%, transparent);
}
}
.border-inherit {
@ -265,28 +271,34 @@ exports[`border-b-* 1`] = `
border-bottom-color: oklab(59.9824% -.067 -.124 / .5);
}
.border-b-\\[color\\:var\\(--my-color\\)\\] {
.border-b-\\[color\\:var\\(--my-color\\)\\], .border-b-\\[color\\:var\\(--my-color\\)\\]\\/50 {
border-bottom-color: var(--my-color);
}
.border-b-\\[color\\:var\\(--my-color\\)\\]\\/50 {
border-bottom-color: color-mix(in oklab, var(--my-color) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.border-b-\\[color\\:var\\(--my-color\\)\\]\\/50 {
border-bottom-color: color-mix(in oklab, var(--my-color) 50%, transparent);
}
}
.border-b-\\[var\\(--my-color\\)\\] {
.border-b-\\[var\\(--my-color\\)\\], .border-b-\\[var\\(--my-color\\)\\]\\/50 {
border-bottom-color: var(--my-color);
}
.border-b-\\[var\\(--my-color\\)\\]\\/50 {
border-bottom-color: color-mix(in oklab, var(--my-color) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.border-b-\\[var\\(--my-color\\)\\]\\/50 {
border-bottom-color: color-mix(in oklab, var(--my-color) 50%, transparent);
}
}
.border-b-current {
.border-b-current, .border-b-current\\/50 {
border-bottom-color: currentColor;
}
.border-b-current\\/50 {
border-bottom-color: color-mix(in oklab, currentcolor 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.border-b-current\\/50 {
border-bottom-color: color-mix(in oklab, currentcolor 50%, transparent);
}
}
.border-b-inherit {
@ -439,28 +451,34 @@ exports[`border-e-* 1`] = `
border-inline-end-color: oklab(59.9824% -.067 -.124 / .5);
}
.border-e-\\[color\\:var\\(--my-color\\)\\] {
.border-e-\\[color\\:var\\(--my-color\\)\\], .border-e-\\[color\\:var\\(--my-color\\)\\]\\/50 {
border-inline-end-color: var(--my-color);
}
.border-e-\\[color\\:var\\(--my-color\\)\\]\\/50 {
border-inline-end-color: color-mix(in oklab, var(--my-color) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.border-e-\\[color\\:var\\(--my-color\\)\\]\\/50 {
border-inline-end-color: color-mix(in oklab, var(--my-color) 50%, transparent);
}
}
.border-e-\\[var\\(--my-color\\)\\] {
.border-e-\\[var\\(--my-color\\)\\], .border-e-\\[var\\(--my-color\\)\\]\\/50 {
border-inline-end-color: var(--my-color);
}
.border-e-\\[var\\(--my-color\\)\\]\\/50 {
border-inline-end-color: color-mix(in oklab, var(--my-color) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.border-e-\\[var\\(--my-color\\)\\]\\/50 {
border-inline-end-color: color-mix(in oklab, var(--my-color) 50%, transparent);
}
}
.border-e-current {
.border-e-current, .border-e-current\\/50 {
border-inline-end-color: currentColor;
}
.border-e-current\\/50 {
border-inline-end-color: color-mix(in oklab, currentcolor 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.border-e-current\\/50 {
border-inline-end-color: color-mix(in oklab, currentcolor 50%, transparent);
}
}
.border-e-inherit {
@ -613,28 +631,34 @@ exports[`border-l-* 1`] = `
border-left-color: oklab(59.9824% -.067 -.124 / .5);
}
.border-l-\\[color\\:var\\(--my-color\\)\\] {
.border-l-\\[color\\:var\\(--my-color\\)\\], .border-l-\\[color\\:var\\(--my-color\\)\\]\\/50 {
border-left-color: var(--my-color);
}
.border-l-\\[color\\:var\\(--my-color\\)\\]\\/50 {
border-left-color: color-mix(in oklab, var(--my-color) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.border-l-\\[color\\:var\\(--my-color\\)\\]\\/50 {
border-left-color: color-mix(in oklab, var(--my-color) 50%, transparent);
}
}
.border-l-\\[var\\(--my-color\\)\\] {
.border-l-\\[var\\(--my-color\\)\\], .border-l-\\[var\\(--my-color\\)\\]\\/50 {
border-left-color: var(--my-color);
}
.border-l-\\[var\\(--my-color\\)\\]\\/50 {
border-left-color: color-mix(in oklab, var(--my-color) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.border-l-\\[var\\(--my-color\\)\\]\\/50 {
border-left-color: color-mix(in oklab, var(--my-color) 50%, transparent);
}
}
.border-l-current {
.border-l-current, .border-l-current\\/50 {
border-left-color: currentColor;
}
.border-l-current\\/50 {
border-left-color: color-mix(in oklab, currentcolor 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.border-l-current\\/50 {
border-left-color: color-mix(in oklab, currentcolor 50%, transparent);
}
}
.border-l-inherit {
@ -787,28 +811,34 @@ exports[`border-r-* 1`] = `
border-right-color: oklab(59.9824% -.067 -.124 / .5);
}
.border-r-\\[color\\:var\\(--my-color\\)\\] {
.border-r-\\[color\\:var\\(--my-color\\)\\], .border-r-\\[color\\:var\\(--my-color\\)\\]\\/50 {
border-right-color: var(--my-color);
}
.border-r-\\[color\\:var\\(--my-color\\)\\]\\/50 {
border-right-color: color-mix(in oklab, var(--my-color) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.border-r-\\[color\\:var\\(--my-color\\)\\]\\/50 {
border-right-color: color-mix(in oklab, var(--my-color) 50%, transparent);
}
}
.border-r-\\[var\\(--my-color\\)\\] {
.border-r-\\[var\\(--my-color\\)\\], .border-r-\\[var\\(--my-color\\)\\]\\/50 {
border-right-color: var(--my-color);
}
.border-r-\\[var\\(--my-color\\)\\]\\/50 {
border-right-color: color-mix(in oklab, var(--my-color) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.border-r-\\[var\\(--my-color\\)\\]\\/50 {
border-right-color: color-mix(in oklab, var(--my-color) 50%, transparent);
}
}
.border-r-current {
.border-r-current, .border-r-current\\/50 {
border-right-color: currentColor;
}
.border-r-current\\/50 {
border-right-color: color-mix(in oklab, currentcolor 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.border-r-current\\/50 {
border-right-color: color-mix(in oklab, currentcolor 50%, transparent);
}
}
.border-r-inherit {
@ -961,28 +991,34 @@ exports[`border-s-* 1`] = `
border-inline-start-color: oklab(59.9824% -.067 -.124 / .5);
}
.border-s-\\[color\\:var\\(--my-color\\)\\] {
.border-s-\\[color\\:var\\(--my-color\\)\\], .border-s-\\[color\\:var\\(--my-color\\)\\]\\/50 {
border-inline-start-color: var(--my-color);
}
.border-s-\\[color\\:var\\(--my-color\\)\\]\\/50 {
border-inline-start-color: color-mix(in oklab, var(--my-color) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.border-s-\\[color\\:var\\(--my-color\\)\\]\\/50 {
border-inline-start-color: color-mix(in oklab, var(--my-color) 50%, transparent);
}
}
.border-s-\\[var\\(--my-color\\)\\] {
.border-s-\\[var\\(--my-color\\)\\], .border-s-\\[var\\(--my-color\\)\\]\\/50 {
border-inline-start-color: var(--my-color);
}
.border-s-\\[var\\(--my-color\\)\\]\\/50 {
border-inline-start-color: color-mix(in oklab, var(--my-color) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.border-s-\\[var\\(--my-color\\)\\]\\/50 {
border-inline-start-color: color-mix(in oklab, var(--my-color) 50%, transparent);
}
}
.border-s-current {
.border-s-current, .border-s-current\\/50 {
border-inline-start-color: currentColor;
}
.border-s-current\\/50 {
border-inline-start-color: color-mix(in oklab, currentcolor 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.border-s-current\\/50 {
border-inline-start-color: color-mix(in oklab, currentcolor 50%, transparent);
}
}
.border-s-inherit {
@ -1135,28 +1171,34 @@ exports[`border-t-* 1`] = `
border-top-color: oklab(59.9824% -.067 -.124 / .5);
}
.border-t-\\[color\\:var\\(--my-color\\)\\] {
.border-t-\\[color\\:var\\(--my-color\\)\\], .border-t-\\[color\\:var\\(--my-color\\)\\]\\/50 {
border-top-color: var(--my-color);
}
.border-t-\\[color\\:var\\(--my-color\\)\\]\\/50 {
border-top-color: color-mix(in oklab, var(--my-color) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.border-t-\\[color\\:var\\(--my-color\\)\\]\\/50 {
border-top-color: color-mix(in oklab, var(--my-color) 50%, transparent);
}
}
.border-t-\\[var\\(--my-color\\)\\] {
.border-t-\\[var\\(--my-color\\)\\], .border-t-\\[var\\(--my-color\\)\\]\\/50 {
border-top-color: var(--my-color);
}
.border-t-\\[var\\(--my-color\\)\\]\\/50 {
border-top-color: color-mix(in oklab, var(--my-color) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.border-t-\\[var\\(--my-color\\)\\]\\/50 {
border-top-color: color-mix(in oklab, var(--my-color) 50%, transparent);
}
}
.border-t-current {
.border-t-current, .border-t-current\\/50 {
border-top-color: currentColor;
}
.border-t-current\\/50 {
border-top-color: color-mix(in oklab, currentcolor 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.border-t-current\\/50 {
border-top-color: color-mix(in oklab, currentcolor 50%, transparent);
}
}
.border-t-inherit {
@ -1309,28 +1351,34 @@ exports[`border-x-* 1`] = `
border-inline-color: oklab(59.9824% -.067 -.124 / .5);
}
.border-x-\\[color\\:var\\(--my-color\\)\\] {
.border-x-\\[color\\:var\\(--my-color\\)\\], .border-x-\\[color\\:var\\(--my-color\\)\\]\\/50 {
border-inline-color: var(--my-color);
}
.border-x-\\[color\\:var\\(--my-color\\)\\]\\/50 {
border-inline-color: color-mix(in oklab, var(--my-color) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.border-x-\\[color\\:var\\(--my-color\\)\\]\\/50 {
border-inline-color: color-mix(in oklab, var(--my-color) 50%, transparent);
}
}
.border-x-\\[var\\(--my-color\\)\\] {
.border-x-\\[var\\(--my-color\\)\\], .border-x-\\[var\\(--my-color\\)\\]\\/50 {
border-inline-color: var(--my-color);
}
.border-x-\\[var\\(--my-color\\)\\]\\/50 {
border-inline-color: color-mix(in oklab, var(--my-color) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.border-x-\\[var\\(--my-color\\)\\]\\/50 {
border-inline-color: color-mix(in oklab, var(--my-color) 50%, transparent);
}
}
.border-x-current {
.border-x-current, .border-x-current\\/50 {
border-inline-color: currentColor;
}
.border-x-current\\/50 {
border-inline-color: color-mix(in oklab, currentcolor 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.border-x-current\\/50 {
border-inline-color: color-mix(in oklab, currentcolor 50%, transparent);
}
}
.border-x-inherit {
@ -1483,28 +1531,34 @@ exports[`border-y-* 1`] = `
border-block-color: oklab(59.9824% -.067 -.124 / .5);
}
.border-y-\\[color\\:var\\(--my-color\\)\\] {
.border-y-\\[color\\:var\\(--my-color\\)\\], .border-y-\\[color\\:var\\(--my-color\\)\\]\\/50 {
border-block-color: var(--my-color);
}
.border-y-\\[color\\:var\\(--my-color\\)\\]\\/50 {
border-block-color: color-mix(in oklab, var(--my-color) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.border-y-\\[color\\:var\\(--my-color\\)\\]\\/50 {
border-block-color: color-mix(in oklab, var(--my-color) 50%, transparent);
}
}
.border-y-\\[var\\(--my-color\\)\\] {
.border-y-\\[var\\(--my-color\\)\\], .border-y-\\[var\\(--my-color\\)\\]\\/50 {
border-block-color: var(--my-color);
}
.border-y-\\[var\\(--my-color\\)\\]\\/50 {
border-block-color: color-mix(in oklab, var(--my-color) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.border-y-\\[var\\(--my-color\\)\\]\\/50 {
border-block-color: color-mix(in oklab, var(--my-color) 50%, transparent);
}
}
.border-y-current {
.border-y-current, .border-y-current\\/50 {
border-block-color: currentColor;
}
.border-y-current\\/50 {
border-block-color: color-mix(in oklab, currentcolor 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.border-y-current\\/50 {
border-block-color: color-mix(in oklab, currentcolor 50%, transparent);
}
}
.border-y-inherit {

View File

@ -328,37 +328,59 @@ export function optimizeAst(
if (polyfills & Polyfills.ColorMix && node.value.includes('color-mix(')) {
let ast = ValueParser.parse(node.value)
let didGenerateFallback = false
ValueParser.walk(ast, (node) => {
let requiresPolyfill = false
ValueParser.walk(ast, (node, { replaceWith }) => {
if (node.kind !== 'function' || node.value !== 'color-mix') return
let containsUnresolvableVars = false
let containsCurrentcolor = false
ValueParser.walk(node.nodes, (node, { replaceWith }) => {
if (node.kind == 'word' && node.value.toLowerCase() === 'currentcolor') {
containsCurrentcolor = true
requiresPolyfill = true
return
}
if (node.kind !== 'function' || node.value !== 'var') return
let firstChild = node.nodes[0]
if (!firstChild || firstChild.kind !== 'word') return
let inlinedColor = designSystem.theme.resolveValue(null, [firstChild.value as any])
if (!inlinedColor) return
requiresPolyfill = true
let inlinedColor = designSystem.theme.resolveValue(null, [firstChild.value as any])
if (!inlinedColor) {
containsUnresolvableVars = true
return
}
didGenerateFallback = true
replaceWith({ kind: 'word', value: inlinedColor })
})
// Change the colorspace to `srgb` since the fallback values should not be represented as
// `oklab(…)` functions again as their support in Safari <16 is very limited.
let colorspace = node.nodes[2]
if (
colorspace.kind === 'word' &&
(colorspace.value === 'oklab' ||
colorspace.value === 'oklch' ||
colorspace.value === 'lab' ||
colorspace.value === 'lch')
) {
colorspace.value = 'srgb'
if (containsUnresolvableVars || containsCurrentcolor) {
let separatorIndex = node.nodes.findIndex(
(node) => node.kind === 'separator' && node.value.trim().includes(','),
)
if (separatorIndex === -1) return
let firstColorValue =
node.nodes.length > separatorIndex ? node.nodes[separatorIndex + 1] : null
if (!firstColorValue) return
replaceWith(firstColorValue)
} else if (requiresPolyfill) {
// Change the colorspace to `srgb` since the fallback values should not be represented as
// `oklab(…)` functions again as their support in Safari <16 is very limited.
let colorspace = node.nodes[2]
if (
colorspace.kind === 'word' &&
(colorspace.value === 'oklab' ||
colorspace.value === 'oklch' ||
colorspace.value === 'lab' ||
colorspace.value === 'lch')
) {
colorspace.value = 'srgb'
}
}
})
if (didGenerateFallback) {
if (requiresPolyfill) {
let fallback = {
...node,
value: ValueParser.toCss(ast),
@ -366,6 +388,7 @@ export function optimizeAst(
let colorMixQuery = rule('@supports (color: color-mix(in lab, red, red))', [node])
parent.push(fallback, colorMixQuery)
return
}
}

View File

@ -291,7 +291,10 @@ describe('theme', async () => {
color: color-mix(in oklab, #ef4444 50%, transparent);
}
.variable {
color: color-mix(in oklab, #ef4444 var(--opacity), transparent);
color: #ef4444;
@supports (color: color-mix(in lab, red, red)) {
color: color-mix(in oklab, #ef4444 var(--opacity), transparent);
}
}
"
`)
@ -365,7 +368,10 @@ describe('theme', async () => {
color: color-mix(in oklab, rgba(255 0 0 / <alpha-value>) 50%, transparent);
}
.css-variable {
color: color-mix(in oklab, rgba(255 0 0 / <alpha-value>) var(--opacity), transparent);
color: rgba(255 0 0 / <alpha-value>);
@supports (color: color-mix(in lab, red, red)) {
color: color-mix(in oklab, rgba(255 0 0 / <alpha-value>) var(--opacity), transparent);
}
}
.js-fraction {
color: color-mix(in oklab, rgb(255 0 0 / 1) 50%, transparent);
@ -374,7 +380,10 @@ describe('theme', async () => {
color: color-mix(in oklab, rgb(255 0 0 / 1) 50%, transparent);
}
.js-variable {
color: color-mix(in oklab, rgb(255 0 0 / 1) var(--opacity), transparent);
color: rgb(255 0 0 / 1);
@supports (color: color-mix(in lab, red, red)) {
color: color-mix(in oklab, rgb(255 0 0 / 1) var(--opacity), transparent);
}
}
"
`)
@ -3749,24 +3758,28 @@ describe('matchUtilities()', () => {
scrollbar-width: 2px;
}
.scrollbar-\\[color\\:var\\(--my-color\\)\\] {
.scrollbar-\\[color\\:var\\(--my-color\\)\\], .scrollbar-\\[color\\:var\\(--my-color\\)\\]\\/50 {
scrollbar-color: var(--my-color);
}
.scrollbar-\\[color\\:var\\(--my-color\\)\\]\\/50 {
scrollbar-color: color-mix(in oklab, var(--my-color) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.scrollbar-\\[color\\:var\\(--my-color\\)\\]\\/50 {
scrollbar-color: color-mix(in oklab, var(--my-color) 50%, transparent);
}
}
.scrollbar-\\[length\\:var\\(--my-width\\)\\] {
scrollbar-width: var(--my-width);
}
.scrollbar-\\[var\\(--my-color\\)\\] {
.scrollbar-\\[var\\(--my-color\\)\\], .scrollbar-\\[var\\(--my-color\\)\\]\\/50 {
scrollbar-color: var(--my-color);
}
.scrollbar-\\[var\\(--my-color\\)\\]\\/50 {
scrollbar-color: color-mix(in oklab, var(--my-color) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.scrollbar-\\[var\\(--my-color\\)\\]\\/50 {
scrollbar-color: color-mix(in oklab, var(--my-color) 50%, transparent);
}
}
.scrollbar-black {
@ -3840,7 +3853,13 @@ describe('matchUtilities()', () => {
).trim(),
).toMatchInlineSnapshot(`
".scrollbar-\\[var\\(--my-color\\)\\]\\/\\[25\\%\\] {
scrollbar-color: color-mix(in oklab, var(--my-color) 25%, transparent);
scrollbar-color: var(--my-color);
}
@supports (color: color-mix(in lab, red, red)) {
.scrollbar-\\[var\\(--my-color\\)\\]\\/\\[25\\%\\] {
scrollbar-color: color-mix(in oklab, var(--my-color) 25%, transparent);
}
}
.scrollbar-black {
@ -3855,12 +3874,14 @@ describe('matchUtilities()', () => {
scrollbar-color: oklab(0% none none / .5);
}
.scrollbar-current {
.scrollbar-current, .scrollbar-current\\/45 {
scrollbar-color: currentcolor;
}
.scrollbar-current\\/45 {
scrollbar-color: color-mix(in oklab, currentcolor 45%, transparent);
@supports (color: color-mix(in lab, red, red)) {
.scrollbar-current\\/45 {
scrollbar-color: color-mix(in oklab, currentcolor 45%, transparent);
}
}"
`)
})

View File

@ -602,7 +602,13 @@ describe('theme(…)', () => {
`),
).toMatchInlineSnapshot(`
".red {
color: color-mix(in oklab, red var(--opacity), transparent);
color: red;
}
@supports (color: color-mix(in lab, red, red)) {
.red {
color: color-mix(in oklab, red var(--opacity), transparent);
}
}"
`)
})
@ -620,7 +626,13 @@ describe('theme(…)', () => {
`),
).toMatchInlineSnapshot(`
".red {
color: color-mix(in oklab, red var(--opacity, 50%), transparent);
color: red;
}
@supports (color: color-mix(in lab, red, red)) {
.red {
color: color-mix(in oklab, red var(--opacity, 50%), transparent);
}
}"
`)
})

View File

@ -258,7 +258,13 @@ describe('arbitrary properties', () => {
it('should generate arbitrary properties with variables and with modifiers', async () => {
expect(await run(['[color:var(--my-color)]/50'])).toMatchInlineSnapshot(`
".\\[color\\:var\\(--my-color\\)\\]\\/50 {
color: color-mix(in oklab, var(--my-color) 50%, transparent);
color: var(--my-color);
}
@supports (color: color-mix(in lab, red, red)) {
.\\[color\\:var\\(--my-color\\)\\]\\/50 {
color: color-mix(in oklab, var(--my-color) 50%, transparent);
}
}"
`)
})
@ -4896,7 +4902,55 @@ describe('`color-mix(…)` polyfill', () => {
`)
})
it('does not replace `currentcolor` inside `color-mix(…)`', async () => {
it('uses the first color value as the fallback when the `color-mix(…)` function contains non-theme variables', async () => {
await expect(
compileCss(
css`
@theme {
--color-red-500: oklch(63.7% 0.237 25.331);
}
@tailwind utilities;
`,
['text-(--my-color)/50', 'text-red-500/(--my-opacity)', 'text-(--my-color)/(--my-opacity)'],
),
).resolves.toMatchInlineSnapshot(`
":root, :host {
--color-red-500: oklch(63.7% .237 25.331);
}
.text-\\(--my-color\\)\\/\\(--my-opacity\\) {
color: var(--my-color);
}
@supports (color: color-mix(in lab, red, red)) {
.text-\\(--my-color\\)\\/\\(--my-opacity\\) {
color: color-mix(in oklab, var(--my-color) var(--my-opacity), transparent);
}
}
.text-\\(--my-color\\)\\/50 {
color: var(--my-color);
}
@supports (color: color-mix(in lab, red, red)) {
.text-\\(--my-color\\)\\/50 {
color: color-mix(in oklab, var(--my-color) 50%, transparent);
}
}
.text-red-500\\/\\(--my-opacity\\) {
color: oklch(63.7% .237 25.331);
}
@supports (color: color-mix(in lab, red, red)) {
.text-red-500\\/\\(--my-opacity\\) {
color: color-mix(in oklab, var(--color-red-500) var(--my-opacity), transparent);
}
}"
`)
})
it('uses the first color value as the fallback when the `color-mix(…)` function contains currentcolor', async () => {
await expect(
compileCss(
css`
@ -4906,7 +4960,60 @@ describe('`color-mix(…)` polyfill', () => {
),
).resolves.toMatchInlineSnapshot(`
".text-current\\/50 {
color: color-mix(in oklab, currentcolor 50%, transparent);
color: currentColor;
}
@supports (color: color-mix(in lab, red, red)) {
.text-current\\/50 {
color: color-mix(in oklab, currentcolor 50%, transparent);
}
}"
`)
})
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 () => {
await expect(
compileCss(
css`
@tailwind utilities;
.stacked {
color: color-mix(
in oklab,
color-mix(in oklab, var(--my-color) var(--my-inner-opacity), transparent)
var(--my-outer-opacity),
transparent
);
}
`,
[],
),
).resolves.toMatchInlineSnapshot(`
".stacked {
color: var(--my-color);
}
@supports (color: color-mix(in lab, red, red)) {
.stacked {
color: color-mix(in oklab, color-mix(in oklab, var(--my-color) var(--my-inner-opacity), transparent) var(--my-outer-opacity), transparent);
}
}"
`)
})
it('does not create a fallback when all color values are statically analyzable (lightningcss will flatten this)', async () => {
await expect(
compileCss(
css`
@theme inline {
--color-red-500: oklch(63.7% 0.237 25.331);
}
@tailwind utilities;
`,
['text-red-500/50'],
),
).resolves.toMatchInlineSnapshot(`
".text-red-500\\/50 {
color: oklab(63.7% .214 .101 / .5);
}"
`)
})

File diff suppressed because it is too large Load Diff