# Mes dégradés sont plus beaux que les vôtres

## ☠️ La Zone Grise de la Mort

Dans les années 2010, nous avons eu le plaisir de découvrir les fonctions `linear-gradient` et `radial-gradient` qui permettent de définir des dégradés directement en CSS.

Je me souviens d'avoir expérimenté diverses combinaisons, avec des résultats assez inégaux. En utilisant certaines couleurs, je voyais une bande grisâtre qui ne semblait pas coller avec l'effet recherché.

Si je vous dis que l'on mélange du bleu et du jaune, on obtient du vert, pas vrai ?

**🟡 + 🔵 = 🟢**

Apparemment pas dans un navigateur, avec CSS.

```plaintext
background: linear-gradient(to right, #FFFF00, #0000FF);
```

![Représentation d'un dégradé linéaire allant du jaune vers le bleu](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v5hz4mn4px9mlia3bh5h.JPG align="left")

J'ai bien tenté de refaire l'expérience avec un outil tel qu'Illustrator et j'ai été surpris d'obtenir un résultat similaire. A l'époque, cela m'avait semblé être une curiosité, un peu décevante mais assez négligeable pour être rapidement oubliée. Elle l'est restée jusqu'à ce que je découvre en 2022 ce tweet de Fin Mourhouse :

%[https://twitter.com/finmoorhouse/status/1543580508508065794] 

Dans ce fil, le chercheur nous explique les origines mathématiques de ce phénomène, parfois appelé *la Zone Grise de la Mort* ☠️

En RVB, on sait comment définir une couleur grise : il suffit de déclarer le même niveau de rouge, de vert et de bleu.

![tableau présentant 11 niveaux de gris allant du blanc au noir avec les quantités correspondantes de RVB](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lvb5bu8zz109n7dulwgi.PNG align="left")

Si le niveau de chaque couleur est élevé, on s'approchera du blanc et inversement, s'il est bas, notre gris tendra vers le noir.

En représentant le RVB sous la forme d'un cube, on peut visualiser "la diagonale du gris" qui le traverse : de l'arrière-plan vers l'avant et du bas vers le haut

![Représentation du modèle RVB sous la forme d'un cube](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xjdgsim74abygr2ocrsj.png align="left")

Source : [Wikimedia](https://commons.wikimedia.org/wiki/File:RGB_color_cube.svg)

Maintenant, pensons à la manière dont notre dégradé est construit.

À chaque pixel situé sur le parcours du dégradé, le navigateur va afficher une couleur qui sera calculée en fonction des valeurs de départ et des valeurs d'arrivée. Il va faire ce calcul pour le rouge, le vert et le bleu : il augmentera ou diminuera régulièrement leur valeur de départ, jusqu'à leur valeur d'arrivée.

Dans notre exemple du jaune et du bleu, nos points de départ et d'arrivée sont soit 0, soit 255. À mi-chemin, il y a un point où toutes les valeurs sont égales. À cet endroit, la synthèse RVB est nécessairement grise :

![La valeur RVB médiane lors d'un dégradé linéaire du jaune vers le bleu et de 127 si l'on va de 255 à 0](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vadi5az3ormty2rmxjr3.JPG align="left")

Notre méthode de calcul du dégradé est facile à comprendre, mais elle nous pousse vers la diagonale du gris et les couleurs semblent plus ternes, moins lumineuses à mesure que nous nous en approchons.

Dans la suite de l'article, nous allons chercher à obtenir un meilleur dégradé, tout en privilégiant une méthode de calcul simple et si possible intuitive.

Voici les problèmes à résoudre :

* éviter la zone grise de la mort
    
* préserver la luminosité des couleurs
    

## 🌈 HSL : oui... Mais non 🌧️

Intuitivement, on peut penser que l'écriture HSL (ou le HWB qui est très proche) est un bon moyen d'éviter la zone grise de la mort.

HSL signifie Hue, Saturation, Lightness (en français : Teinte, Saturation, Lumière). Ce système représente les couleurs sur un disque :

* **les teintes** (H) sont réparties de 0 à 360°.
    
* **la saturation** (S) de la teinte est exprimée par le rayon du disque de 0 (la couleur totalement desaturée, donc grise) à 100 (la saturation est maximale).
    

On y ajoute une troisième dimension :

* **la lumière** (L) qui va de 0 (aucune luminosité, la couleur est noire) à 100 (luminosité maximale, la couleur est blanche).
    

On peut alors imaginer un dégradé qui ferait varier uniquement la teinte, tout en gardant une saturation et une luminosité constante. Voici la visualisation d'un déplacement sur le disque qui évite la zone grise :

![Disque chromatique représentant le système HSL avec une flèche traçant le parcours d'un dégradé allant du jaune au bleu ](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yrx90stx5pc6mpgt22e7.png align="left")

Source : [Wikimedia](https://commons.wikimedia.org/wiki/File:Color_wheel_with_degree.png)

Cela semble une bonne piste. Mais attention, déclarer nos couleurs avec `hsl()` au lieu de `rgb()` ne va pas suffire. `hsl()` est un "sucre syntaxique" : la valeur déclarée sera systématiquement convertie en RVB et nous obtiendrons le même résultat qu'avant.

Ce qui nous intéresse, ce n'est pas comment on déclare une couleur, mais **comment on calcule notre dégradé**.

C'est là qu'intervient la spécification CSS *Color Module Level 4* qui introduit la fonction `color-interpolation-method` qui nous permet de spécifier un mode de calcul.

Cela nous donne :

```plaintext
background: linear-gradient(to right in hsl, #0000ff, #ffff00);
```

![Dégradé CSS du jaune au bleu qui utilise une interpolation HSL](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zwn95mqyvpsds9a4tbly.JPG align="left")

> ⚠️ A ce jour (mars 2024) la fonction [`color-interpolation-method`](https://developer.mozilla.org/en-US/docs/Web/CSS/color-interpolation-method) pour les dégradés n'est pas disponible dans tous les navigateurs.

On voit que nous n'avons même pas besoin de déclarer nos couleurs en HSL, ce qui importe c'est la déclaration `in hsl`.

Grâce à cette méthode, nous évitons la zone grise et nous obtenons enfin notre couleur verte entre le bleu et le jaune.

Mais le résultat est un peu déroutant. En allant vers le bleu, on passe par une zone turquoise qui parait franchement plus lumineuse que l'ensemble du dégradé. Ça ne semble pas très naturel.

## 💡 sRGB : des problèmes de luminosité

Quand nous utilisons les méthodes `rgb()`, `hsl()`, `hwb()`, nous définissons des couleurs contenues dans l'**espace colorimétrique sRGB** (Le "s" signifie *standard*). Cet espace sert à déclarer les couleurs disponibles sur la plupart des écrans que nous utilisons. Il est très pratique, car il permet d'en définir un très grand nombre en combinant seulement 3 signaux lumineux. Mais il a aussi ses défauts.

Les couleurs de cet espace ont été définies pour nos yeux, mais elles sont aussi contraintes par la technologie, par la conception même des écrans.

Il en va que le sRGB contient des ajustements spécifiques par rapport à la luminosité réelle : **la correction Gamma**.

Mais aussi que les valeurs maximales de rouge, vert et bleu ne sont pas égales en termes de **luminosité perçue** par nos yeux (on parlera alors de "luminance").

Ces deux points ont un impact important sur les calculs que nous voulons effectuer pour créer notre dégradé.

### La correction Gamma 🕶️

Pour des raisons pratiques la répartition de la luminosité dans le modèle RVB est artificiellement corrigée par rapport à la luminosité naturelle.

Nous percevons mieux les nuances sombres que claires. Or, si l'on découpait linéairement la lumière selon sa luminosité, nous disposerions de beaucoup plus de nuances claires et pas assez de nuances sombres.

Voici un schéma avec à gauche le découpage corrigé et à droite un découpage linéaire :

![Tableau comparant le découpage de la luminosité, selon qu'il soit linéaire ou si on lui a appliqué une correction Gamma](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x7r9qk7z4dlctscsveid.JPG align="left")

Dans la version corrigée, la répartition des nuances sombres nous semble plus adaptée à nos besoins. Dans le découpage linéaire, la quantité de nuances claires paraît trop importante.

La correction est de plus en plus importante au fur et à mesure que la luminosité augmente. Cette non-linéarité implique des calculs plus compliqués pour réaliser un dégradé avec une luminosité constante. Il serait plus efficace d'effectuer nos calculs à partir des valeurs du RVB linéaire.

Nous retrouvons alors notre fonction `color-interpolation-method`. Elle nous permet de spécifier des espaces de couleur différents dans nos dégradés :

```plaintext
background:linear-gradient(to right in srgb, #FFFF00, #0000FF);
background:linear-gradient(to right in srgb-linear, #FFFF00, #0000FF);
```

![Comparaison d'un dégradé du jaune vers le bleu, le premier utilise l'espace RVB classique et le second, le RVB linéaire ce qui permet de garder une luminosité constante des couleurs dans le dégradé](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xxpkegx412aqcsh6ise1.JPG align="left")

Le premier dégradé utilise l'espace RVB "classique" (sRGB), le second utilise le RVB linéaire.

La différence de luminosité entre les deux méthodes de calcul est flagrante : sombre pour la première, plus lumineuse pour la seconde... Mais dans les deux cas subsiste notre premier problème : nous passons par le gris.

### La luminosité perçue 👁️

Deuxième problème de notre espace sRGB : les valeurs maximales n'ont pas une luminosité équivalente, en tout cas dans notre perception. C'est flagrant si l'on compare le vert et le bleu :

![Comparaison d'un aplat rgb vert pur et d'un autre bleu pur avec indication des codes RGB et HSL correspondants](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mq39ryv6lnhvdh5o6w7r.png align="left")

![Comparaison d'un aplat rgb vert et d'un autre bleu, tous les deux convertis en niveau de gris pour montrer la différence de luminosité](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yljc9comf9hhxci4xcy7.png align="left")

La conversion en niveaux de gris rend cette différence encore plus évidente. Ces différences de luminosité ne sont pas prises en compte sur le disque chromatique que nous utilisons avec HSL.

Le modèle HSL est destiné à l'espace sRGB, et donc les valeurs de luminosité et de saturation sont harmonisées afin de créer un disque chromatique cohérent avec cet espace.

Ce schéma représente horizontalement les teintes (Hue) et verticalement la luminosité (Lightness) :

![Schéma représentant la répartition de la luminosité pour chaque teinte, en utilisant la méthode hsl()](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fkll675rk6pxtcqt02io.PNG align="left")

Source : [Wikimedia](https://commons.wikimedia.org/wiki/File:Hsl-hsv_models_b.svg)

Si l'on se place à 50% de luminosité (axe vertical L) et que l'on parcourt les différentes couleurs, la luminosité est loin de nous paraître constante.

Ceci explique le manque de consistance dans notre dégradé HSL.

À ce stade, on se dit que prendre en compte l'ensemble des caractéristiques du sRGB et les intégrer dans nos calculs de dégradé risque d'être un peu compliqué. En s'appliquant, on doit pouvoir y arriver, mais ce n'est pas aussi intuitif qu'on le voudrait et difficilement automatisable.

Mais alors que faire ?

Prenons un peu de recul et revenons sur cette histoire d'espace colorimétrique.

## 👩‍🚀 L'espace colorimétrique sRGB

Nous travaillons en sRGB car c'est ce dont les écrans ont besoin et historiquement nous avons les outils CSS pour transmettre cette information dans cet espace de couleur. Mais cet espace ne contient pas toutes les couleurs possibles, il est restreint. Cet ensemble de couleurs, parfois appelé *gamut*, peut être élargi. Certains appareils vont proposer des *gamut* couvrant une plage de couleur plus importante. C'est le cas de certains Macbook qui utilisent le gamut *P3* ou d'écrans de télé HD qui utilisent *rec2020*.

Le schéma *CIE 1931* ci-dessous propose une représentation mathématique des couleurs visibles. Sur cette version, on a indiqué les plages des différents gamuts :

![schéma CIE 1931 avec représentation des principaux gamut disponibles](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b4xj17ouqr3j2szdekac.PNG align="left")

Source : [Wikimedia](https://commons.wikimedia.org/wiki/File:CIE1931xy_gamut_comparison_of_sRGB_P3_Rec2020.svg)

Au milieu, notre sRGB est un triangle, avec ses trois sommets : rouge, vert et bleu. Chacune de ces valeurs est envoyée à l'écran et sera affichée par un pixel donné, ce qui produira pour nos yeux la couleur finale.

Pour explorer cet espace, nous utilisons rgb() et hsl(). Mais comme on le disait, certains appareils nous permettent d'accéder à d'autres espaces colorimétriques. Alors comment y définir des couleurs ?

## 👽 Plus d'espace : plus de modèle !

C'est là qu'intervient de nouveau le *CSS Color Module Level 4*. Cette spécification nous met à disposition des méthodes permettant de définir des couleurs dans et hors du sRGB. Ces nouveaux modèles ne sont pas contraints par un gamut spécifique et ne sont pas conçus comme le sRGB.

Nous allons nous intéresser à :

* lab()
    
* oklab()
    
* lch()
    
* oklch()
    

Ces fonctions permettent de déclarer une couleur dans l'espace colorimétrique L*a*b\*. Cet espace couvre toutes les couleurs visibles par nos yeux et il a été créé pour prendre en compte notre perception, en particulier la différence de luminosité que nous percevons en fonction des teintes.

La fonction lab() permet de déclarer une couleur selon sa luminosité (l) et sa teinte selon deux axes (a et b) qui vont respectivement du vert au rouge et du bleu au jaune.

lch() permet d'exploiter cet espace mais sous une forme cylindrique (comme le HSL).

Les versions "ok" (oklab et oklch) corrigent certains défauts du lab (notamment dans la luminosité des bleus).

Avec ce modèle, nous contrôlons :

* la luminosité (l)
    
* le chroma (c) qui indique la quantité de couleur et que l'on peut rapprocher de la saturation
    
* la teinte (h) qui désigne une couleur sur un cercle
    

Nous allons donc refaire notre dégradé en okLCH qui semble être le candidat parfait :

* Interpolation circulaire
    
* Prise en compte de la luminosité perçue
    

### 🍭 Bon ! Et notre dégradé alors ?

```plaintext
background:linear-gradient(to right in okLCH, #ffff00, #0000ff)
```

![Dégradé CSS du jaune au bleu qui utilise une interpolation okLCH](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cbqa2q6demf14tlnwkp0.JPG align="left")

Et enfin ! On peut dire que ce dégradé correspond à l'idée que nous en étions faite. Ça été un peu plus long que prévu, mais cela nous a permis de comprendre l’intérêt des différents modèles à notre disposition.

Je ne vais pas rentrer plus en avant sur les qualités de okLCH, cela me demanderait beaucoup de travail et cet article serait interminable. Et surtout cela a déjà été très bien fait par l'agence **Evil Martians**, dont je ne saurais trop vous conseiller l'excellent article :

* [OKLCH in CSS: why we moved from RGB and HSL](https://evilmartians.com/chronicles/oklch-in-css-why-quit-rgb-hsl)
    

Ainsi que leur color picker qui permet de convertir des couleurs en okLCH et aussi de visualiser nos couleurs dans les limites de différents gamuts :

* [LCH Color Picker & Converter](https://lch.oklch.com/#70,39,239,100)
    

## 🎨 okLCH au service du Design System

Au-delà des dégradés, ce qui est intéressant, c'est la consistance d'un modèle comme okLCH pour systématiser nos déclinaisons de couleur. Générer une palette de couleur cohérente à partir d'un ensemble de teintes sera plus simple, notamment si l'on fait usage de la fonction CSS `color-mix()`.

Pour découvrir ce sujet, je vous conseille [la vidéo de Kevin Powell sur la fonction CSS `color-mix()`](https://www.youtube.com/watch?v=I9zHX-jSKpA) qui nous explique comment décliner ses couleurs et comment les méthodes d'interpolation vont influencer les résultats.

%[https://www.youtube.com/watch?v=I9zHX-jSKpA] 

## 🛠️ Et en prod, ça marche ?

### color-interpolation-method

#### 1\. dans les dégradés

Rappelons-le, `color-interpolation-method`, n'est pas disponible partout pour les dégradés (notamment dans Firefox). Si vous l'utilisez, vous devrez employer un fallback.

Il est aussi possible d'utiliser un générateur de dégradé qui simule des interpolations spécifiques, par exemple :

* [Vivid Gradient Generator Tool](https://www.learnui.design/tools/gradient-generator.html)
    

#### 2\. dans color-mix()

L'usage d'une méthode d'interpolation avec color-mix() est supporté par les navigateurs récents

* [Voir la doc de Mdn sur color-interpolation-method](https://developer.mozilla.org/en-US/docs/Web/CSS/color-interpolation-method)
    

### oklch()

Il est possible de déclarer vos couleurs directement en utilisant `oklch()`. [La compatibilité semble plutôt bonne](https://caniuse.com/mdn-css_types_color_oklch).

## Conclusion

En partant de cette curiosité graphique qu'est "la zone grise de la mort", je ne pensais pas découvrir autant de problématiques liées à la couleur. En tant que dev front, la couleur peut sembler être un sujet annexe qui concerne essentiellement le design. Mais en creusant, on comprend très vite que l'usage de la couleur sur nos écrans est très lié à la technologie et aux évolutions techniques. Comprendre cette histoire et ces contraintes, c'est aussi mieux comprendre et exploiter les outils à notre disposition.

Les fonctionnalités récentes du CSS en matière de couleur peuvent largement impacter le travail des design.ers.euses et leur ouvrir de **nouvelles perspectives créatives**.

Il est donc important de réfléchir ce sujet ensemble, à ce stade les possibilités offertes par CSS et les navigateurs semblent plus en avance que celles offertes par les outils de design. Ainsi, dans **Figma**, il n'est pas possible d'utiliser nativement des modèles "exotiques" comme okLCH ou d'utiliser des méthodes d'interpolation autre que le RVB pour les dégradés. Il existe cependant des plugins, reste à voir s'ils sont efficaces.

## Bibliographie

### Gamut / Espaces colorimétriques

* [C'est quoi un Espace de couleurs ? sRGB, rec. 2020 (HDR), Adobe RGB, rec. 709 - L'Atelier du câble](https://www.latelierducable.com/tv-televiseur/cest-quoi-un-espace-de-couleurs-srgb-rec-2020-hdr-adobe-rgb-rec-709/)
    
* [color-gamut - CSS: Cascading Style Sheets | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/color-gamut)
    
* [New CSS color spaces and functions in all major engines](https://web.dev/color-spaces-and-functions/)
    
* [Examples of various wide-gamut images](https://webkit.org/blog-files/color-gamut/)
    
* [Wide Gamut Color in CSS with Display-P3 | WebKit](https://webkit.org/blog/10042/wide-gamut-color-in-css-with-display-p3/)
    
* [Color Spaces and Colors in CSS –](https://www.bram.us/2022/02/08/color-spaces-and-colors-in-css/)[Bram.us](http://Bram.us)
    

### Dégradés

* [Gradient Color Spaces Exploration](https://codepen.io/argyleink/pen/OJObWEW)
    
* [Easy CSS Gradient Generator Tool (avoids gray dead zones ☠️)](https://www.learnui.design/tools/gradient-generator.html)
    
* [Perceptual gradients - Share an idea - Figma Community Forum](https://forum.figma.com/t/perceptual-gradients/11205)
    
* [Understanding CSS Color Gradients for the Front-end and Data Visualization | by Alexandre Lopes | Medium](https://aexklon.medium.com/understanding-css-color-gradients-for-the-front-end-and-data-visualization-4887b8cc3581)
    

### Gamma

* [sRGB, RGB & Gamma / Sébastien Pierre | Observable](https://observablehq.com/@sebastien/srgb-rgb-gamma)
    
* [Linear, Gamma and sRGB Color Spaces](https://matt77hias.github.io/blog/2018/07/01/linear-gamma-and-sRGB-color-spaces.html)
    
* [What are the practical differences when working with colors in a linear vs. a non-linear RGB space? - Stack Overflow](https://stackoverflow.com/questions/12524623/what-are-the-practical-differences-when-working-with-colors-in-a-linear-vs-a-no)
    

### Luminosité perçue

* [Mixing Colours of Equal Luminance — Part 1](https://medium.com/sketch-app-sources/mixing-colours-of-equal-luminance-part-1-41f69518d647)
    

### Models

* [OKLCH Color Picker & Converter](https://oklch.com/#93.06,0.323,332,100)
    
* [OKLCH in CSS: why we moved from RGB and HSL—Martian Chronicles, Evil Martians’ team blog](https://evilmartians.com/chronicles/oklch-in-css-why-quit-rgb-hsl)
    
* [Lch and Lab colour and gradient picker](https://davidjohnstone.net/lch-lab-colour-gradient-picker)
    
* [LCH colors in CSS: what, why, and how? • Lea Verou](https://lea.verou.me/blog/2020/04/lch-colors-in-css-what-why-and-how/)
    
* [The CIELAB L*a*b\* System – the Method to Quantify Colors of Coatings - Prospector Knowledge Center](https://www.ulprospector.com/knowledge/10780/pc-the-cielab-lab-system-the-method-to-quantify-colors-of-coatings/)
    
* [Are you using the WRONG color model in your CSS? - YouTube](https://www.youtube.com/watch?v=Ab9pHqhsfcc)
    
* [Online Color Converter - Colormath](https://ajalt.github.io/colormath/converter/)
    
* [Visual representation of color names in different color models](https://codepen.io/meodai/pen/zdgXJj)
    
* [hue-interpolation-method - CSS: Cascading Style Sheets | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/hue-interpolation-method)
    
* [CSS Color Module Level 4](https://www.w3.org/TR/css-color-4/#ok-lab)
    

### Color-mix

* [Create a color theme with CSS Relative Color Syntax, CSS color-mix(), and CSS color-contrast() –](https://www.bram.us/2021/04/28/create-a-color-theme-with-css-relative-color-syntax-css-color-mix-and-css-color-contrast/)[Bram.us](http://Bram.us)
    
* [A deep dive into CSS color-mix() - YouTube](https://www.youtube.com/watch?v=I9zHX-jSKpA)
    

*Illustration :*[*Susan Wilkinson*](https://unsplash.com/fr/@susan_wilkinson)
