Revert "Don't use color-mix(…) on currentColor (#17247)" and work around Preflight crash (#17306)

Closes #17194.

This reverts commit d6d913ec39.

The initial fix does breaks older versions of Chrome (where text won't
render with a color for the placeholder at all anymore) and the usage of
the _relative colors_ features also means it'll require a much newer
version of Safari/Firefox/Chrome to work correctly. The implementation
was also wrong as it always set alpha to the specific percent instead of
applying it additively (note that this can be fixed with `calc(alpha *
opacity)` though).

Instead we decided to fix this by adding a `@supports` query to
Preflight that only targets browsers that aren't affected by the crash.
We currently use the following workaround:

```css
/*
  Set the default placeholder color to a semi-transparent version of the current text color in browsers that do not
  crash when using `color-mix(…)` with `currentColor`. (https://github.com/tailwindlabs/tailwindcss/issues/17194)
*/

@supports (not (-webkit-appearance: -apple-pay-button)) /* Not Safari */ or
  (contain-intrinsic-size: 1px) /* Safari 17+ */ {
  ::placeholder {
    color: color-mix(in oklab, currentColor 50%, transparent);
  }
}
```

## Test plan

When testing the `color-mix(currentColor)` vs `oklab(from currentColor
…)` we created the following support matrix. I'm extending it with _our
fix_ which is the fix ended up using:

| Browser | Version | `color-mix(… currentColor …)` | `oklab(from
currentColor …)` | `@supports { color-mix(…) }` |
| ------- | ------- | ----------------------------- |
---------------------------- | ------- |
| Chrome | 111 | ❌ | ❌ | ❌ |
| Chrome | 116 | ✅ | ❌ | ✅ |
| Chrome | 131+ | ✅ | ✅ | ✅ |
| Safari | 16.4 | 💥 | ❌ | ❌ |
| Safari | 16.6+ | ✅ | ❌ | ❌ |
| Safari | 18+ | ✅ | ✅ | ✅ |
| Firefox | 128 | ✅ | ❌ | ✅ |
| Firefox | 133 | ✅ | ✅ | ✅ |

Note that on Safari 16, this change makes it so that the browser does
not crash yet it still won't work either. That's because now the browser
will fall back to the default placeholder color instead. We used the
following play to test the fix: https://play.tailwindcss.com/RF1RYbZLKY
This commit is contained in:
Philipp Spiess 2025-03-20 17:43:32 +01:00 • committed by GitHub
parent 3f313b4eb2
commit 40a76e3380
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 61 additions and 48 deletions

View file

@ -24,9 +24,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix incorrect angle in `-bg-conic-*` utilities ([#17174](https://github.com/tailwindlabs/tailwindcss/pull/17174))
- Fix `border-[12px_4px]` being interpreted as a `border-color` instead of a `border-width` ([#17248](https://github.com/tailwindlabs/tailwindcss/pull/17248))
- 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))
- Work around a crash in Safari 16.4 and 16.5 when using the default Preflight styles ([#17306](https://github.com/tailwindlabs/tailwindcss/pull/17306))
- Pre-process `<template lang="…">` in Vue files ([#17252](https://github.com/tailwindlabs/tailwindcss/pull/17252))
- Ensure that all CSS variables used by preflight are prefixed ([#17036](https://github.com/tailwindlabs/tailwindcss/pull/17036))
- Ensure that all CSS variables used by Preflight are prefixed ([#17036](https://github.com/tailwindlabs/tailwindcss/pull/17036))
### Changed

View file

@ -161,7 +161,12 @@ exports[`\`@import 'tailwindcss'\` is replaced with the generated CSS 1`] = `
::placeholder {
opacity: 1;
color: oklab(from currentColor l a b / 50%);
}
@supports (not ((-webkit-appearance: -apple-pay-button))) or (contain-intrinsic-size: 1px) {
::placeholder {
color: color-mix(in oklab, currentColor 50%, transparent);
}
}
textarea {

View file

@ -276,13 +276,23 @@ textarea,
}
/*
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
2. Set the default placeholder color to a semi-transparent version of the current text color. We use the `oklab(…)` function to work around an issue in Safari 16.4 and 16.5. (https://github.com/tailwindlabs/tailwindcss/issues/17194)
Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
*/
::placeholder {
opacity: 1; /* 1 */
color: oklab(from currentColor l a b / 50%); /* 2 */
opacity: 1;
}
/*
Set the default placeholder color to a semi-transparent version of the current text color in browsers that do not
crash when using `color-mix(…)` with `currentColor`. (https://github.com/tailwindlabs/tailwindcss/issues/17194)
*/
@supports (not (-webkit-appearance: -apple-pay-button)) /* Not Safari */ or
(contain-intrinsic-size: 1px) /* Safari 17+ */ {
::placeholder {
color: color-mix(in oklab, currentColor 50%, transparent);
}
}
/*

View file

@ -250,7 +250,12 @@ exports[`compiling CSS > prefix all CSS variables inside preflight 1`] = `
::placeholder {
opacity: 1;
color: oklab(from currentColor l a b / 50%);
}
@supports (not ((-webkit-appearance: -apple-pay-button))) or (contain-intrinsic-size: 1px) {
::placeholder {
color: color-mix(in oklab, currentColor 50%, transparent);
}
}
textarea {

View file

@ -104,7 +104,7 @@ exports[`border-* 1`] = `
}
.border-current\\/50 {
border-color: oklab(from currentColor l a b / 50%);
border-color: color-mix(in oklab, currentColor 50%, transparent);
}
.border-inherit {
@ -246,7 +246,7 @@ exports[`border-b-* 1`] = `
}
.border-b-current\\/50 {
border-bottom-color: oklab(from currentColor l a b / 50%);
border-bottom-color: color-mix(in oklab, currentColor 50%, transparent);
}
.border-b-inherit {
@ -388,7 +388,7 @@ exports[`border-e-* 1`] = `
}
.border-e-current\\/50 {
border-inline-end-color: oklab(from currentColor l a b / 50%);
border-inline-end-color: color-mix(in oklab, currentColor 50%, transparent);
}
.border-e-inherit {
@ -530,7 +530,7 @@ exports[`border-l-* 1`] = `
}
.border-l-current\\/50 {
border-left-color: oklab(from currentColor l a b / 50%);
border-left-color: color-mix(in oklab, currentColor 50%, transparent);
}
.border-l-inherit {
@ -672,7 +672,7 @@ exports[`border-r-* 1`] = `
}
.border-r-current\\/50 {
border-right-color: oklab(from currentColor l a b / 50%);
border-right-color: color-mix(in oklab, currentColor 50%, transparent);
}
.border-r-inherit {
@ -814,7 +814,7 @@ exports[`border-s-* 1`] = `
}
.border-s-current\\/50 {
border-inline-start-color: oklab(from currentColor l a b / 50%);
border-inline-start-color: color-mix(in oklab, currentColor 50%, transparent);
}
.border-s-inherit {
@ -956,7 +956,7 @@ exports[`border-t-* 1`] = `
}
.border-t-current\\/50 {
border-top-color: oklab(from currentColor l a b / 50%);
border-top-color: color-mix(in oklab, currentColor 50%, transparent);
}
.border-t-inherit {
@ -1098,7 +1098,7 @@ exports[`border-x-* 1`] = `
}
.border-x-current\\/50 {
border-inline-color: oklab(from currentColor l a b / 50%);
border-inline-color: color-mix(in oklab, currentColor 50%, transparent);
}
.border-x-inherit {
@ -1240,7 +1240,7 @@ exports[`border-y-* 1`] = `
}
.border-y-current\\/50 {
border-block-color: oklab(from currentColor l a b / 50%);
border-block-color: color-mix(in oklab, currentColor 50%, transparent);
}
.border-y-inherit {

View file

@ -3860,7 +3860,7 @@ describe('matchUtilities()', () => {
}
.scrollbar-current\\/45 {
scrollbar-color: oklab(from currentColor l a b / 45%);
scrollbar-color: color-mix(in oklab, currentColor 45%, transparent);
}"
`)
})

View file

@ -8058,7 +8058,7 @@ test('accent', async () => {
}
.accent-current\\/50, .accent-current\\/\\[0\\.5\\], .accent-current\\/\\[50\\%\\] {
accent-color: oklab(from currentColor l a b / 50%);
accent-color: color-mix(in oklab, currentColor 50%, transparent);
}
.accent-inherit {
@ -8173,7 +8173,7 @@ test('caret', async () => {
}
.caret-current\\/50, .caret-current\\/\\[0\\.5\\], .caret-current\\/\\[50\\%\\] {
caret-color: oklab(from currentColor l a b / 50%);
caret-color: color-mix(in oklab, currentColor 50%, transparent);
}
.caret-inherit {
@ -8286,7 +8286,7 @@ test('divide-color', async () => {
}
:where(.divide-current\\/50 > :not(:last-child)), :where(.divide-current\\/\\[0\\.5\\] > :not(:last-child)), :where(.divide-current\\/\\[50\\%\\] > :not(:last-child)) {
border-color: oklab(from currentColor l a b / 50%);
border-color: color-mix(in oklab, currentColor 50%, transparent);
}
:where(.divide-inherit > :not(:last-child)) {
@ -10248,11 +10248,11 @@ test('bg', async () => {
}
.bg-current\\/50, .bg-current\\/\\[0\\.5\\], .bg-current\\/\\[50\\%\\] {
background-color: oklab(from currentColor l a b / 50%);
background-color: color-mix(in oklab, currentColor 50%, transparent);
}
.bg-current\\/\\[var\\(--bg-opacity\\)\\] {
background-color: oklab(from currentColor l a b / var(--bg-opacity));
background-color: color-mix(in oklab, currentColor var(--bg-opacity), transparent);
}
.bg-inherit {
@ -10770,11 +10770,11 @@ test('bg', async () => {
),
).toMatchInlineSnapshot(`
".bg-current\\/custom {
background-color: oklab(from currentColor l a b / var(--opacity-custom, var(--custom-opacity)));
background-color: color-mix(in oklab, currentColor var(--opacity-custom, var(--custom-opacity)), transparent);
}
.bg-current\\/half {
background-color: oklab(from currentColor l a b / var(--opacity-half, .5));
background-color: color-mix(in oklab, currentColor var(--opacity-half, .5), transparent);
}
.\\[color\\:red\\]\\/half {
@ -10868,7 +10868,7 @@ test('from', async () => {
}
.from-current\\/50, .from-current\\/\\[0\\.5\\], .from-current\\/\\[50\\%\\] {
--tw-gradient-from: oklab(from currentColor l a b / 50%);
--tw-gradient-from: color-mix(in oklab, currentColor 50%, transparent);
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
@ -11094,7 +11094,7 @@ test('via', async () => {
}
.via-current\\/50, .via-current\\/\\[0\\.5\\], .via-current\\/\\[50\\%\\] {
--tw-gradient-via: oklab(from currentColor l a b / 50%);
--tw-gradient-via: color-mix(in oklab, currentColor 50%, transparent);
--tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-via-stops);
}
@ -11316,7 +11316,7 @@ test('to', async () => {
}
.to-current\\/50, .to-current\\/\\[0\\.5\\], .to-current\\/\\[50\\%\\] {
--tw-gradient-to: oklab(from currentColor l a b / 50%);
--tw-gradient-to: color-mix(in oklab, currentColor 50%, transparent);
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
@ -11843,7 +11843,7 @@ test('fill', async () => {
}
.fill-current\\/50, .fill-current\\/\\[0\\.5\\], .fill-current\\/\\[50\\%\\] {
fill: oklab(from currentColor l a b / 50%);
fill: color-mix(in oklab, currentColor 50%, transparent);
}
.fill-inherit {
@ -11980,7 +11980,7 @@ test('stroke', async () => {
}
.stroke-current\\/50, .stroke-current\\/\\[0\\.5\\], .stroke-current\\/\\[50\\%\\] {
stroke: oklab(from currentColor l a b / 50%);
stroke: color-mix(in oklab, currentColor 50%, transparent);
}
.stroke-inherit {
@ -12956,7 +12956,7 @@ test('placeholder', async () => {
}
.placeholder-current\\/50::placeholder, .placeholder-current\\/\\[0\\.5\\]::placeholder, .placeholder-current\\/\\[50\\%\\]::placeholder {
color: oklab(from currentColor l a b / 50%);
color: color-mix(in oklab, currentColor 50%, transparent);
}
.placeholder-inherit::placeholder {
@ -13105,9 +13105,9 @@ test('decoration', async () => {
}
.decoration-current\\/50, .decoration-current\\/\\[0\\.5\\], .decoration-current\\/\\[50\\%\\] {
-webkit-text-decoration-color: oklab(from currentColor l a b / 50%);
-webkit-text-decoration-color: oklab(from currentColor l a b / 50%);
text-decoration-color: oklab(from currentColor l a b / 50%);
-webkit-text-decoration-color: color-mix(in oklab, currentColor 50%, transparent);
-webkit-text-decoration-color: color-mix(in oklab, currentColor 50%, transparent);
text-decoration-color: color-mix(in oklab, currentColor 50%, transparent);
}
.decoration-inherit {
@ -14810,7 +14810,7 @@ test('outline', async () => {
}
.outline-current\\/50, .outline-current\\/\\[0\\.5\\], .outline-current\\/\\[50\\%\\] {
outline-color: oklab(from currentColor l a b / 50%);
outline-color: color-mix(in oklab, currentColor 50%, transparent);
}
.outline-inherit {
@ -15260,7 +15260,7 @@ test('text', async () => {
}
.text-current\\/50, .text-current\\/\\[0\\.5\\], .text-current\\/\\[50\\%\\] {
color: oklab(from currentColor l a b / 50%);
color: color-mix(in oklab, currentColor 50%, transparent);
}
.text-inherit {
@ -15429,7 +15429,7 @@ test('shadow', async () => {
}
.shadow-current\\/50, .shadow-current\\/\\[0\\.5\\], .shadow-current\\/\\[50\\%\\] {
--tw-shadow-color: oklab(from currentColor l a b / 50%);
--tw-shadow-color: color-mix(in oklab, currentColor 50%, transparent);
}
.shadow-inherit {
@ -15651,7 +15651,7 @@ test('inset-shadow', async () => {
}
.inset-shadow-current\\/50, .inset-shadow-current\\/\\[0\\.5\\], .inset-shadow-current\\/\\[50\\%\\] {
--tw-inset-shadow-color: oklab(from currentColor l a b / 50%);
--tw-inset-shadow-color: color-mix(in oklab, currentColor 50%, transparent);
}
.inset-shadow-inherit {
@ -15889,7 +15889,7 @@ test('ring', async () => {
}
.ring-current\\/50, .ring-current\\/\\[0\\.5\\], .ring-current\\/\\[50\\%\\] {
--tw-ring-color: oklab(from currentColor l a b / 50%);
--tw-ring-color: color-mix(in oklab, currentColor 50%, transparent);
}
.ring-inherit {
@ -16228,7 +16228,7 @@ test('inset-ring', async () => {
}
.inset-ring-current\\/50, .inset-ring-current\\/\\[0\\.5\\], .inset-ring-current\\/\\[50\\%\\] {
--tw-inset-ring-color: oklab(from currentColor l a b / 50%);
--tw-inset-ring-color: color-mix(in oklab, currentColor 50%, transparent);
}
.inset-ring-inherit {
@ -16472,7 +16472,7 @@ test('ring-offset', async () => {
}
.ring-offset-current\\/50, .ring-offset-current\\/\\[0\\.5\\], .ring-offset-current\\/\\[50\\%\\] {
--tw-ring-offset-color: oklab(from currentColor l a b / 50%);
--tw-ring-offset-color: color-mix(in oklab, currentColor 50%, transparent);
}
.ring-offset-inherit {

View file

@ -136,13 +136,6 @@ export function withAlpha(value: string, alpha: string): string {
alpha = `${alphaAsNumber * 100}%`
}
// Use the `oklab(…)` function when applying alpha to `currentColor` to work
// around a crash with Safari 16.4 and 16.5
// https://github.com/tailwindlabs/tailwindcss/issues/17194
if (value === 'currentColor') {
return `oklab(from currentColor l a b / ${alpha})`
}
return `color-mix(in oklab, ${value} ${alpha}, transparent)`
}