---
title: "Deux couleurs entrent. Poline décide par où passer"
locale: "fr"
url: "https://irz.fr/fr/articles/poline-color-path-fr"
markdown_url: "https://irz.fr/fr/articles/poline-color-path-fr.md"
category: "creative"
tags: ["couleur", "creative coding", "Poline", "HSL", "accessibilité"]
published_at: "2026-08-20T12:32:00.000Z"
author: "Manon Girard"
translation: "https://irz.fr/en/articles/poline-color-path-en.md"
---

# Deux couleurs entrent. Poline décide par où passer

Poline transforme deux couleurs en points géométriques avant de les relier. Le détour produit des palettes surprenantes, mais ni une uniformité perceptuelle ni un contraste accessible automatiques.

Un générateur de palettes classique nous pousse souvent à penser en cases : choisir un bleu, choisir un orange, puis demander quelques couleurs « entre les deux ». **Poline pense davantage en trajet.**

La micro-lib de David Aerne prend des couleurs d’ancrage, les transforme en coordonnées, trace un chemin entre elles et échantillonne ce chemin pour récupérer de nouvelles couleurs.[1](https://meodai.github.io/poline/)[2](https://github.com/meodai/poline/tree/c0bf44b06d549eb7bd5ecc1ac84cd3c58eb296cc)

Cette différence paraît poétique jusqu’au moment où deux ancres opposées font traverser la trajectoire par le noir.

C’est là que Poline devient vraiment intéressant : il ne cherche pas à être un nuancier scientifiquement uniforme. Il expose une petite géométrie que l’on peut tordre.

## Pas du OKLCH

Commençons par éviter un malentendu assez facile en 2026.

Poline **ne travaille pas en OKLCH**. La version npm actuelle, 0.13.1, est une bibliothèque MIT sans dépendance runtime qui accepte et produit essentiellement des couleurs HSL.[2](https://github.com/meodai/poline/tree/c0bf44b06d549eb7bd5ecc1ac84cd3c58eb296cc)[3](https://www.npmjs.com/package/poline/v/0.13.1)

Le code transforme une ancre `[hue, saturation, lightness]` dans un espace interne à trois coordonnées. Le hue devient un angle autour d’un cercle ; la lightness détermine la distance au centre ; la saturation devient l’axe `z`.[2](https://github.com/meodai/poline/tree/c0bf44b06d549eb7bd5ecc1ac84cd3c58eb296cc)

Puis les points intermédiaires sont calculés sur `x`, `y` et `z`, avant une conversion inverse vers HSL.[2](https://github.com/meodai/poline/tree/c0bf44b06d549eb7bd5ecc1ac84cd3c58eb296cc)

> Illustration: Schéma expliquant la transformation HSL vers les coordonnées XYZ internes de Poline puis le retour vers HSL. Le XYZ de Poline est un espace géométrique interne. Il ne faut pas le confondre avec le CIE XYZ colorimétrique. Credit: Analyse et illustration IRZ à partir de Poline 0.13.1.

Le nom `xyz` utilisé dans l’API peut d’ailleurs induire en erreur : ces valeurs ne sont **pas** le CIE XYZ de la science des couleurs. Elles servent ici de coordonnées cartésiennes pour cette représentation polaire maison.[2](https://github.com/meodai/poline/tree/c0bf44b06d549eb7bd5ecc1ac84cd3c58eb296cc)

Voilà pourquoi la bibliothèque peut faire des choses que l’interpolation directe de HSL ne fait pas naturellement.

## Une droite

Avec une seule fonction de position appliquée aux trois axes, Poline garde les points sur une **droite** entre deux ancres dans son espace interne. La fonction change seulement leur répartition le long du segment.[2](https://github.com/meodai/poline/tree/c0bf44b06d549eb7bd5ecc1ac84cd3c58eb296cc)

Par défaut, cette fonction est sinusoïdale. On peut la remplacer par une progression linéaire, exponentielle, quadratique, cubique, quartique, `smoothstep` ou plusieurs autres variantes.[2](https://github.com/meodai/poline/tree/c0bf44b06d549eb7bd5ecc1ac84cd3c58eb296cc)

C’est déjà utile. Dix couleurs régulièrement espacées en temps n’ont aucune obligation d’être régulièrement espacées le long d’un chemin si l’easing accélère puis ralentit.

Mais le vrai tour commence lorsque `x`, `y` et `z` reçoivent **des fonctions différentes**.

## Une courbe

La documentation propose précisément ce montage : sinus sur `x`, fonction quadratique sur `y`, progression linéaire sur `z`.[2](https://github.com/meodai/poline/tree/c0bf44b06d549eb7bd5ecc1ac84cd3c58eb296cc)

À un instant donné, les trois coordonnées n’ont alors pas parcouru la même fraction de leur distance. Le point quitte la droite reliant les ancres et décrit une courbe.

Autrement dit, on ne demande plus seulement : « quelles sont les couleurs entre A et B ? »

On demande : **« quel chemin voulons-nous emprunter de A vers B ? »**

La nuance peut sembler abstraite, alors nous avons pris un exemple directement dans le README de Poline.

## Bleu orange

La documentation montre un gradient entre ces deux ancres :

`HSL(210, 80%, 60%) → HSL(30, 80%, 60%)`

Ce sont un bleu et un orange exactement opposés de 180° en hue.[2](https://github.com/meodai/poline/tree/c0bf44b06d549eb7bd5ecc1ac84cd3c58eb296cc)

Nous avons généré dix swatches de trois façons : une interpolation HSL naïve conservant saturation et lightness, Poline avec position linéaire, puis Poline avec le trio de fonctions d’axe documenté plus haut.

> Illustration: Comparaison de trois chemins entre un bleu HSL 210 et un orange HSL 30 : interpolation HSL naïve, Poline linéaire et Poline courbé. Les mêmes ancres ne racontent pas du tout la même histoire. Le HSL naïf traverse cyan, vert et jaune ; la droite Poline plonge vers presque noir ; le trajet courbé passe par violet, magenta et rouge. Credit: Benchmark IRZ avec Poline 0.13.1.

Le HSL naïf conserve `L=60%` et passe par cyan, vert puis jaune. La version Poline linéaire fait quelque chose de radicalement différent : ses ancres étant opposées sur le cercle, la droite interne passe près du centre, donc la lightness HSL descend jusqu’à **6,67 %** avant de remonter.

Avec la fonction sinusoïdale par défaut, un point de notre même test atteint même une lightness numériquement quasi nulle. On obtient du noir au milieu du voyage.

Ce comportement découle directement du modèle : **dans Poline, le centre du cercle correspond à `L=0`**.[2](https://github.com/meodai/poline/tree/c0bf44b06d549eb7bd5ecc1ac84cd3c58eb296cc)

Ce n’est pas une erreur. C’est le prix, ou le plaisir, d’avoir choisi une géométrie créative plutôt qu’une interpolation pensée pour conserver une propriété perceptuelle particulière.

## Distances réelles

Nous avons ensuite converti les swatches sRGB en Oklab et mesuré la distance euclidienne entre couleurs adjacentes. Oklab est précisément conçu pour que des distances similaires correspondent mieux à des différences perçues similaires que HSL.[5](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Colors/Color_values)[7](https://www.w3.org/TR/css-color-4/#ok-lab)

Sur **ce seul exemple**, le rapport entre le plus grand et le plus petit pas vaut :

- **6,55×** pour notre interpolation HSL naïve ;
- **2,46×** pour Poline linéaire ;
- **2,78×** pour la trajectoire Poline courbée.

C’est un résultat amusant : Poline donne ici des pas moins inégaux que notre HSL naïf, alors qu’il ne travaille pas dans un espace perceptuellement uniforme.

Il ne faut surtout pas en faire un classement général. Changez les ancres, l’easing ou le nombre de points et les chiffres changent. Poline n’optimise aucun ΔE dans son algorithme.[2](https://github.com/meodai/poline/tree/c0bf44b06d549eb7bd5ecc1ac84cd3c58eb296cc)

La conclusion raisonnable est seulement que **géométrie différente implique distribution différente**, et qu’il faut mesurer ce qui compte pour l’usage final plutôt que déduire la qualité du nom du modèle.

## Faux OKLCH

La version 0.13.1 ajoute un détail particulièrement important si l’on veut exporter ces couleurs pour le web : `colorsCSSoklch` et `oklchCSS` existent bien.[2](https://github.com/meodai/poline/tree/c0bf44b06d549eb7bd5ecc1ac84cd3c58eb296cc)[3](https://www.npmjs.com/package/poline/v/0.13.1)

Mais le commentaire du code est explicite : il s’agit d’une **approximation par remise à l’échelle du HSL, pas d’une vraie conversion colorimétrique**.[2](https://github.com/meodai/poline/tree/c0bf44b06d549eb7bd5ecc1ac84cd3c58eb296cc)

Poline transforme simplement :

- `HSL lightness` → `OKLCH L` ;
- `HSL saturation × 0,4` → `OKLCH C` ;
- `HSL hue` → `OKLCH h`.

Le problème est que les nombres portent alors un autre sens. MDN rappelle que la lightness OKLCH est une lightness perçue, contrairement au `L` de HSL.[4](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/color_value/oklch)[5](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Colors/Color_values)

Notre ancre bleue permet de voir l’écart. Poline exporte :

`oklch(60% 0.320 210)`

La **conversion colorimétrique réelle du HSL d’origine en sRGB** donne dans notre calcul environ :

`oklch(66.96% 0.145 250.90)`

Même histoire pour l’orange : Poline écrit `60% 0.320 30`, alors que la couleur HSL correspondante se situe autour de `75.06% 0.137 62.81` une fois réellement convertie.

Ces chaînes CSS sont valides syntaxiquement, mais **elles décrivent d’autres couleurs**. Pour conserver la couleur tout en changeant de représentation, le README recommande d’ailleurs d’utiliser une bibliothèque de conversion comme Culori.[2](https://github.com/meodai/poline/tree/c0bf44b06d549eb7bd5ecc1ac84cd3c58eb296cc)

## Contraste séparé

La seconde frontière est l’accessibilité.

Une palette peut être harmonieuse, spectaculaire et complètement inadaptée à du texte. WCAG 2.2 demande au niveau AA un contraste d’au moins **4,5:1 pour le texte normal**, avec une exception à 3:1 pour le grand texte.[6](https://www.w3.org/TR/WCAG22/#contrast-minimum)

Nous avons calculé le contraste des dix swatches de notre trajet courbé contre blanc et noir.

> Illustration: Dix couleurs du trajet Poline avec leurs rapports de contraste WCAG contre texte noir et blanc. Aucune couleur de foreground fixe ne fonctionne sur toute cette trajectoire. Le blanc passe 7 fois sur 10, le noir 3 fois sur 10 ; en choisissant l’un ou l’autre swatch par swatch, les dix peuvent dépasser 4,5:1. Credit: Calcul et illustration IRZ, formule WCAG 2.2.

Un texte **toujours blanc** passe sur 7 swatches sur 10. Un texte **toujours noir** ne passe que sur 3.

Dans cet exemple précis, chaque swatch possède néanmoins au moins un choix entre noir et blanc qui dépasse 4,5:1. Cela suffit pour montrer la séparation des responsabilités : Poline fabrique le trajet ; le système de design doit encore choisir les rôles, les foregrounds et les paires acceptables.

## Pas un système

C’est probablement la meilleure manière d’utiliser Poline : **comme générateur de matière**, pas comme validateur de système.

Pour une visualisation générative, une animation, une série de posters ou un thème qui doit évoluer continûment, `getColorAt(t)` est particulièrement séduisant : une valeur entre 0 et 1 permet désormais d’échantillonner n’importe où sur le voyage complet, même avec plusieurs segments et easings.[2](https://github.com/meodai/poline/tree/c0bf44b06d549eb7bd5ecc1ac84cd3c58eb296cc)

Pour des tokens d’interface, la tâche continue après Poline. Il faut tester le contraste, éventuellement contrôler les distances perceptuelles, décider quelles couleurs peuvent porter du texte, vérifier les états interactifs et ne pas supposer que deux swatches distincts à l’œil d’un concepteur le seront pour tous les utilisateurs.

OKLCH peut être une bonne étape suivante parce que sa lightness et ses distances ont une signification perceptuelle plus utile que celles de HSL.[4](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/color_value/oklch)[5](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Colors/Color_values) Mais **écrire `oklch(...)` ne suffit pas** : il faut réellement convertir ou travailler dans cet espace.

## Le bon détour

Poline est une petite bibliothèque, MIT, sans dépendance runtime, et cette modestie fait partie de son intérêt.[2](https://github.com/meodai/poline/tree/c0bf44b06d549eb7bd5ecc1ac84cd3c58eb296cc)[3](https://www.npmjs.com/package/poline/v/0.13.1)

Elle ne prétend pas résoudre toute la colorimétrie. Son README la décrit même avec une bonne dose d’auto-dérision comme une sorte de sorcellerie par coordonnées polaires.[2](https://github.com/meodai/poline/tree/c0bf44b06d549eb7bd5ecc1ac84cd3c58eb296cc)

Le mécanisme réel tient en quelques idées : transformer HSL en points, relier les ancres, appliquer des easings, convertir les points obtenus en nouvelles couleurs.

La contribution la plus forte n’est donc pas une nouvelle palette.

C’est de nous forcer à voir qu’**entre deux couleurs, le chemin est déjà une décision de design**.

## References

1. [Poline — démo et documentation](https://meodai.github.io/poline/)
2. [meodai/poline — dépôt GitHub, commit audité c0bf44b](https://github.com/meodai/poline/tree/c0bf44b06d549eb7bd5ecc1ac84cd3c58eb296cc)
3. [npm, poline 0.13.1](https://www.npmjs.com/package/poline/v/0.13.1)
4. [MDN, oklch() CSS function](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/color_value/oklch)
5. [MDN, CSS color values — LCH and OkLCh](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Colors/Color_values)
6. [W3C, WCAG 2.2 — Contrast (Minimum)](https://www.w3.org/TR/WCAG22/#contrast-minimum)
7. [W3C, CSS Color Module Level 4 — Oklab and OkLCh](https://www.w3.org/TR/css-color-4/#ok-lab)
