OKLCH no CSS: cores e paletas que fazem sentido
OKLCH é uma forma de descrever cores por luz (L), croma (C, a intensidade) e matiz (H, o ângulo no círculo de cores), num espaço que acompanha a percepção do olho. No CSS, oklch(62% 0.19 260) é um azul. A vantagem sobre o HSL é que a luz é confiável: tons com o mesmo L parecem igualmente claros, o que facilita criar paletas, estados de hover e temas escuros. Todos os navegadores atuais aceitam oklch().
O problema do HSL
No HSL, a luminosidade é uma conta simples sobre os canais RGB, e não a luz que o olho percebe. Por isso hsl(60 100% 50%), um amarelo, parece muito mais claro que hsl(240 100% 50%), um azul, mesmo com os dois em 50%. Uma paleta feita girando o matiz no HSL sai desigual: algumas cores gritam, outras somem, e o contraste do texto muda de uma para outra.
O que é o OKLCH
O OKLCH é a forma polar do OKLab, um espaço de cor publicado por Björn Ottosson em 2020 e adotado pelo CSS Color 4. O L vai de 0% (preto) a 100% (branco) e acompanha a percepção. O C é o croma: 0 é cinza, e valores em torno de 0,1 a 0,3 são cores vivas. O H é o matiz em graus: perto de 30 é vermelho, 140 verde e 260 azul.
Para ver qualquer cor em OKLCH, cole o HEX no conversor de cores. Em código, a conversão passa pelo RGB linear e por duas multiplicações de matriz:
// HEX para OKLCH (Björn Ottosson). Devolve l de 0 a 1, c (croma) e h em graus.
function hexParaOklch(hex) {
const n = parseInt(hex.replace('#', ''), 16);
const [r, g, b] = [(n >> 16) & 255, (n >> 8) & 255, n & 255].map((v) => {
const c = v / 255;
return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4; // sRGB para luz linear
});
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
const L = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s;
const A = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s;
const B = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s;
const C = Math.hypot(A, B);
const H = C < 1e-4 ? 0 : ((Math.atan2(B, A) * 180) / Math.PI + 360) % 360;
return { l: L, c: C, h: H };
}
const { l, c, h } = hexParaOklch('#3b82f6');
console.log(`oklch(${(l * 100).toFixed(1)}% ${c.toFixed(3)} ${h.toFixed(1)})`); // oklch(62.3% 0.188 259.8) HEX para OKLCH, com as matrizes publicadas por Ottosson.
import math
def hex_para_oklch(cor: str) -> tuple[float, float, float]:
"""HEX para OKLCH (Björn Ottosson): l de 0 a 1, croma e matiz em graus."""
h = cor.lstrip("#")
def linear(v: int) -> float:
c = v / 255
return c / 12.92 if c <= 0.04045 else ((c + 0.055) / 1.055) ** 2.4
r, g, b = (linear(int(h[i : i + 2], 16)) for i in (0, 2, 4))
l = (0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b) ** (1 / 3)
m = (0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b) ** (1 / 3)
s = (0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b) ** (1 / 3)
L = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s
A = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s
B = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s
C = math.hypot(A, B)
H = 0.0 if C < 1e-4 else math.degrees(math.atan2(B, A)) % 360
return L, C, H
L, C, H = hex_para_oklch("#3b82f6")
print(f"oklch({L * 100:.1f}% {C:.3f} {H:.1f})") # oklch(62.3% 0.188 259.8) Paletas e variações com OKLCH
Mantendo o C e o H e mudando só o L, você tem tons da mesma família, do quase branco ao quase preto. É assim que o gerador de paleta monta a escala de 50 a 950. Para um hover, baixe a luz alguns pontos: oklch(from var(--marca) calc(l - 0.08) c h), com a sintaxe de cor relativa do CSS.
- Cores de estado (sucesso, alerta, erro) com o mesmo L ficam com peso visual parecido.
- No tema escuro, inverter a escala de luz mantém a hierarquia da interface.
- Como o L é confiável, fica mais fácil prever o contraste: o cinza #767676, o mais claro que passa no AA sobre branco, tem L de 56,6%, e o #595959, que passa no AAA, tem 46,4%. Em cores com croma, os valores mudam um pouco, então confira no verificador de contraste.
Cuidados
Nem toda combinação de L, C e H cabe no sRGB, o espaço das telas comuns. Uma cor com croma alto e luz extrema pode não existir nele, e o navegador a aproxima. Telas de gama ampla (P3) mostram mais cores. Ao gerar paletas em código, reduza o croma até a cor caber, mantendo luz e matiz, como faz o gerador do PraDev.
Para navegadores muito antigos, mantenha um valor em HEX antes: color: #3b82f6; color: oklch(62.3% 0.188 259.8);. O navegador que não entende a segunda linha fica com a primeira.
Achou um erro neste guia? Avise por e-mail.
Ferramentas para usar junto
- Conversor de Cores HEX RGB HSLConverta cores entre HEX, RGB, HSL, OKLCH, HWB, HSV e CMYK, com seletor de cores e os nomes do CSS
- Gerador de Paleta a partir de HEXGere uma escala de tons de 50 a 950 e combinações (complementar, análogas, tríade) a partir de uma cor
- Verificador de ContrasteConfira o contraste entre a cor do texto e a do fundo pela WCAG 2.2: razão, níveis AA e AAA e sugestões de cor que passam
Perguntas frequentes
Os navegadores aceitam oklch()?
Sim. Chrome, Edge, Firefox e Safari aceitam oklch() desde 2023. Para navegadores mais antigos, declare antes a mesma cor em HEX.
Qual a diferença entre OKLCH e LCH?
O LCH vem do CIELAB, de 1976, que distorce os tons azuis ao mudar a luz. O OKLCH corrige isso e é mais previsível para gerar paletas. O CSS aceita os dois.
Devo trocar todas as cores do projeto para OKLCH?
Não precisa. HEX continua ótimo para guardar cores. O OKLCH ajuda na hora de criar variações e paletas, e você pode converter de um para o outro quando quiser.