Interpolate gradients using OKLCH by default (#14708)

This PR updates all of our gradient utilities to interpolate using OKLCH
by default instead of sRGB. This results in a smoother transition
between colors that preserves saturation throughout the gradient, rather
than hitting the dreaded dull gray zone in between your color stops.

Here are a few examples comparing sRGB (top) to OKLCH (bottom):

<img width="736" alt="image"
src="https://github.com/user-attachments/assets/57a158b6-a3a2-4eda-813e-1b596c7d4b3a">

We only apply a default interpolation mode when _not_ using arbitrary
values with the gradient utility.

Simplified but clear:

```css
.bg-linear-to-r {
  background-image: linear-gradient(to right in oklch, var(--gradient-color-stops));
}

.bg-linear-[to_right] {
  background-image: linear-gradient(to right, var(--gradient-color-stops));
}

.bg-linear-[to_right_in_hsl] {
  background-image: linear-gradient(to right in hsl, var(--gradient-color-stops));
}
```

---------

Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
This commit is contained in:
Adam Wathan 2024-10-17 16:16:14 -04:00 • committed by GitHub
parent 72c30d422a
commit edb066e0aa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 50 additions and 43 deletions

View file

@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add first draft of new wide-gamut color palette ([#14693](https://github.com/tailwindlabs/tailwindcss/pull/14693))
- Support linear gradient angles as bare values ([#14707](https://github.com/tailwindlabs/tailwindcss/pull/14707))
- Interpolate gradients in OKLCH by default ([#14708](https://github.com/tailwindlabs/tailwindcss/pull/14708))
- _Upgrade (experimental)_: Migrate `theme(…)` calls to `var(…)` or to the modern `theme(…)` syntax ([#14664](https://github.com/tailwindlabs/tailwindcss/pull/14664), [#14695](https://github.com/tailwindlabs/tailwindcss/pull/14695))
### Fixed

View file

@ -9557,8 +9557,8 @@ test('bg', async () => {
}
.-bg-linear-45 {
--tw-gradient-position: calc(45deg * -1), ;
background-image: linear-gradient(var(--tw-gradient-stops, calc(45deg * -1)));
--tw-gradient-position: calc(45deg * -1) in oklch, ;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.-bg-linear-\\[1\\.3rad\\] {
@ -9572,48 +9572,48 @@ test('bg', async () => {
}
.bg-gradient-to-b {
--tw-gradient-position: to bottom, ;
--tw-gradient-position: to bottom in oklch, ;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-gradient-to-bl {
--tw-gradient-position: to bottom left, ;
--tw-gradient-position: to bottom left in oklch, ;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-gradient-to-br {
--tw-gradient-position: to bottom right, ;
--tw-gradient-position: to bottom right in oklch, ;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-gradient-to-l {
--tw-gradient-position: to left, ;
--tw-gradient-position: to left in oklch, ;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-gradient-to-r {
--tw-gradient-position: to right, ;
--tw-gradient-position: to right in oklch, ;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-gradient-to-t {
--tw-gradient-position: to top, ;
--tw-gradient-position: to top in oklch, ;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-gradient-to-tl {
--tw-gradient-position: to top left, ;
--tw-gradient-position: to top left in oklch, ;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-gradient-to-tr {
--tw-gradient-position: to top right, ;
--tw-gradient-position: to top right in oklch, ;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-45 {
--tw-gradient-position: 45deg, ;
background-image: linear-gradient(var(--tw-gradient-stops, 45deg));
--tw-gradient-position: 45deg in oklch, ;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-\\[1\\.3rad\\] {
@ -9632,42 +9632,42 @@ test('bg', async () => {
}
.bg-linear-to-b {
--tw-gradient-position: to bottom, ;
--tw-gradient-position: to bottom in oklch, ;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-to-bl {
--tw-gradient-position: to bottom left, ;
--tw-gradient-position: to bottom left in oklch, ;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-to-br {
--tw-gradient-position: to bottom right, ;
--tw-gradient-position: to bottom right in oklch, ;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-to-l {
--tw-gradient-position: to left, ;
--tw-gradient-position: to left in oklch, ;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-to-r {
--tw-gradient-position: to right, ;
--tw-gradient-position: to right in oklch, ;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-to-t {
--tw-gradient-position: to top, ;
--tw-gradient-position: to top in oklch, ;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-to-tl {
--tw-gradient-position: to top left, ;
--tw-gradient-position: to top left in oklch, ;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-to-tr {
--tw-gradient-position: to top right, ;
--tw-gradient-position: to top right in oklch, ;
background-image: linear-gradient(var(--tw-gradient-stops));
}

View file

@ -2516,12 +2516,12 @@ export function createUtilities(theme: Theme) {
['tl', 'top left'],
]) {
staticUtility(`bg-gradient-to-${value}`, [
['--tw-gradient-position', `to ${direction},`],
['--tw-gradient-position', `to ${direction} in oklch,`],
['background-image', `linear-gradient(var(--tw-gradient-stops))`],
])
staticUtility(`bg-linear-to-${value}`, [
['--tw-gradient-position', `to ${direction},`],
['--tw-gradient-position', `to ${direction} in oklch,`],
['background-image', `linear-gradient(var(--tw-gradient-stops))`],
])
}
@ -2558,8 +2558,8 @@ export function createUtilities(theme: Theme) {
value = withNegative(`${value}deg`, candidate)
return [
decl('--tw-gradient-position', `${value},`),
decl('background-image', `linear-gradient(var(--tw-gradient-stops,${value}))`),
decl('--tw-gradient-position', `${value} in oklch,`),
decl('background-image', `linear-gradient(var(--tw-gradient-stops))`),
]
}
})
@ -2569,7 +2569,7 @@ export function createUtilities(theme: Theme) {
if (!candidate.value) {
return [
decl('--tw-gradient-position', `initial`),
decl('--tw-gradient-position', `in oklch,`),
decl('background-image', `conic-gradient(var(--tw-gradient-stops))`),
]
}
@ -2587,7 +2587,7 @@ export function createUtilities(theme: Theme) {
value = withNegative(`${value}deg`, candidate)
return [
decl('--tw-gradient-position', `from ${value},`),
decl('--tw-gradient-position', `from ${value} in oklch,`),
decl('background-image', `conic-gradient(var(--tw-gradient-stops))`),
]
}
@ -2598,7 +2598,7 @@ export function createUtilities(theme: Theme) {
if (!candidate.value) {
return [
decl('--tw-gradient-position', `initial`),
decl('--tw-gradient-position', `in oklch,`),
decl('background-image', `radial-gradient(var(--tw-gradient-stops))`),
]
}

View file

@ -30,37 +30,40 @@ test('touch action', async ({ page }) => {
for (let [classes, expected] of [
[
'bg-linear-to-r from-red',
'linear-gradient(to right, rgb(255, 0, 0) 0%, rgba(0, 0, 0, 0) 100%)',
'linear-gradient(to right in oklch, rgb(255, 0, 0) 0%, rgba(0, 0, 0, 0) 100%)',
],
[
'bg-linear-to-r via-red',
'linear-gradient(to right, rgba(0, 0, 0, 0) 0%, rgb(255, 0, 0) 50%, rgba(0, 0, 0, 0) 100%)',
'linear-gradient(to right in oklch, rgba(0, 0, 0, 0) 0%, rgb(255, 0, 0) 50%, rgba(0, 0, 0, 0) 100%)',
],
[
'bg-linear-to-r to-red',
'linear-gradient(to right in oklch, rgba(0, 0, 0, 0) 0%, rgb(255, 0, 0) 100%)',
],
['bg-linear-to-r to-red', 'linear-gradient(to right, rgba(0, 0, 0, 0) 0%, rgb(255, 0, 0) 100%)'],
[
'bg-linear-to-r from-red to-blue',
'linear-gradient(to right, rgb(255, 0, 0) 0%, rgb(0, 0, 255) 100%)',
'linear-gradient(to right in oklch, rgb(255, 0, 0) 0%, rgb(0, 0, 255) 100%)',
],
[
'bg-linear-45 from-red to-blue',
'linear-gradient(45deg, rgb(255, 0, 0) 0%, rgb(0, 0, 255) 100%)',
'linear-gradient(45deg in oklch, rgb(255, 0, 0) 0%, rgb(0, 0, 255) 100%)',
],
[
'-bg-linear-45 from-red to-blue',
// Chrome reports a different (but also correct) computed value than Firefox/WebKit so we check
// for both options.
[
'linear-gradient(-45deg, rgb(255, 0, 0) 0%, rgb(0, 0, 255) 100%)',
'linear-gradient(calc(-45deg), rgb(255, 0, 0) 0%, rgb(0, 0, 255) 100%)',
'linear-gradient(-45deg in oklch, rgb(255, 0, 0) 0%, rgb(0, 0, 255) 100%)',
'linear-gradient(calc(-45deg) in oklch, rgb(255, 0, 0) 0%, rgb(0, 0, 255) 100%)',
],
],
[
'bg-linear-to-r via-red to-blue',
'linear-gradient(to right, rgba(0, 0, 0, 0) 0%, rgb(255, 0, 0) 50%, rgb(0, 0, 255) 100%)',
'linear-gradient(to right in oklch, rgba(0, 0, 0, 0) 0%, rgb(255, 0, 0) 50%, rgb(0, 0, 255) 100%)',
],
[
'bg-linear-to-r from-red via-green to-blue',
'linear-gradient(to right, rgb(255, 0, 0) 0%, rgb(0, 255, 0) 50%, rgb(0, 0, 255) 100%)',
'linear-gradient(to right in oklch, rgb(255, 0, 0) 0%, rgb(0, 255, 0) 50%, rgb(0, 0, 255) 100%)',
],
[
'bg-linear-[to_right,var(--color-red),var(--color-green),var(--color-blue)]',
@ -88,13 +91,13 @@ test('background gradient, going from 2 to 3', async ({ page }) => {
)
expect(await getPropertyValue('#x', 'background-image')).toEqual(
'linear-gradient(to right, rgb(255, 0, 0) 0%, rgb(0, 0, 255) 100%)',
'linear-gradient(to right in oklch, rgb(255, 0, 0) 0%, rgb(0, 0, 255) 100%)',
)
await page.locator('#x').hover()
expect(await getPropertyValue('#x', 'background-image')).toEqual(
'linear-gradient(to right, rgb(255, 0, 0) 0%, rgb(0, 255, 0) 50%, rgb(0, 0, 255) 100%)',
'linear-gradient(to right in oklch, rgb(255, 0, 0) 0%, rgb(0, 255, 0) 50%, rgb(0, 0, 255) 100%)',
)
})
@ -109,19 +112,22 @@ test('background gradient, going from 3 to 2', async ({ page }) => {
)
expect(await getPropertyValue('#x', 'background-image')).toEqual(
'linear-gradient(to right, rgb(255, 0, 0) 0%, rgb(0, 255, 0) 50%, rgb(0, 0, 255) 100%)',
'linear-gradient(to right in oklch, rgb(255, 0, 0) 0%, rgb(0, 255, 0) 50%, rgb(0, 0, 255) 100%)',
)
await page.locator('#x').hover()
expect(await getPropertyValue('#x', 'background-image')).toEqual(
'linear-gradient(to right, rgb(255, 0, 0) 0%, rgb(0, 0, 255) 100%)',
'linear-gradient(to right in oklch, rgb(255, 0, 0) 0%, rgb(0, 0, 255) 100%)',
)
})
for (let [classes, expected] of [
['bg-conic from-red', 'conic-gradient(rgb(255, 0, 0) 0%, rgba(0, 0, 0, 0) 100%)'],
['bg-conic-45 from-red', 'conic-gradient(from 45deg, rgb(255, 0, 0) 0%, rgba(0, 0, 0, 0) 100%)'],
['bg-conic from-red', 'conic-gradient(in oklch, rgb(255, 0, 0) 0%, rgba(0, 0, 0, 0) 100%)'],
[
'bg-conic-45 from-red',
'conic-gradient(from 45deg in oklch, rgb(255, 0, 0) 0%, rgba(0, 0, 0, 0) 100%)',
],
[
'bg-conic-[from_45deg] from-red',
'conic-gradient(from 45deg, rgb(255, 0, 0) 0%, rgba(0, 0, 0, 0) 100%)',
@ -142,7 +148,7 @@ for (let [classes, expected] of [
}
for (let [classes, expected] of [
['bg-radial from-red', 'radial-gradient(rgb(255, 0, 0) 0%, rgba(0, 0, 0, 0) 100%)'],
['bg-radial from-red', 'radial-gradient(in oklch, rgb(255, 0, 0) 0%, rgba(0, 0, 0, 0) 100%)'],
[
'bg-radial-[at_0%_0%] from-red',
'radial-gradient(at 0% 0%, rgb(255, 0, 0) 0%, rgba(0, 0, 0, 0) 100%)',