Cores no CSS moderno: oklch(), color-mix() e mais
O CSS moderno ganhou funções de cor que resolvem problemas antigos do HEX e do HSL. As mais importantes são oklch(), que descreve a cor por luminosidade, croma e matiz de forma perceptualmente uniforme, e color-mix(), que mistura duas cores direto no navegador. Somadas à sintaxe de cor relativa e a light-dark(), elas permitem gerar escalas, estados de hover e temas escuros a partir de uma única variável.
Essas funções fazem parte das especificações CSS Color Level 4 e 5. Este guia mostra a sintaxe de cada uma com exemplos reais, explica quando usar e termina com um roteiro para migrar uma paleta HEX para OKLCH sem quebrar nada.
A nova sintaxe de rgb() e hsl()
A primeira mudança é pequena, mas aparece em todo lugar: os valores agora são separados por espaços, e a transparência vem depois de uma barra. As funções rgba() e hsla() continuam funcionando, mas deixaram de ser necessárias.
/* sintaxe antiga */
color: rgba(255, 87, 51, 0.5);
/* sintaxe moderna */
color: rgb(255 87 51);
color: rgb(255 87 51 / 50%);
color: hsl(11 100% 60% / 0.8);
Todas essas linhas descrevem o mesmo laranja, #FF5733, com ou sem transparência. O alpha aceita número (0.5) ou porcentagem (50%). Todas as funções novas deste guia seguem esse padrão.
hwb(), lab() e lch()
hwb()
hwb() descreve a cor por matiz, quantidade de branco e quantidade de preto, parecido com misturar tinta. É intuitivo para clarear ou escurecer à mão:
color: hwb(11 20% 0%); /* = #FF5733 */
color: hwb(11 20% 30%); /* o mesmo matiz, mais escuro */
lab() e lch()
lab() e lch() usam o espaço CIELAB, criado para aproximar a percepção humana. Em lab(), o primeiro valor é a luminosidade e os outros dois são eixos (verde–vermelho e azul–amarelo). Em lch(), os eixos viram croma (intensidade) e matiz (ângulo):
color: lab(61% 63.5 56); /* ≈ #FF5733 */
color: lch(61% 84.7 41.4); /* a mesma cor em coordenadas polares */
O CIELAB tem uma distorção conhecida: ao variar a luminosidade de alguns azuis, o matiz desliza para o roxo. Foi para corrigir isso que surgiu o OKLab.
oklab() e oklch(): cores perceptualmente uniformes
O OKLab, publicado por Björn Ottosson em 2020, mantém a ideia do Lab com matizes mais estáveis. O oklch() é a versão polar dele e o formato mais prático para o dia a dia:
color: oklch(0.68 0.21 33.7); /* ≈ #FF5733 */
color: oklch(68% 0.21 33.7 / 50%); /* com transparência */
color: oklab(0.68 0.175 0.116); /* a mesma cor em oklab */
- L (luminosidade): de 0 (preto) a 1 ou 100% (branco).
- C (croma): de 0 (cinza) para cima; na prática, raramente passa de 0,37.
- H (matiz): ângulo de 0 a 360.
Por que isso importa
No HSL, o L de 50% não significa a mesma claridade para todos os matizes. Compare o amarelo e o azul puros:
| Cor | HSL | OKLCH |
|---|---|---|
#FFFF00 | hsl(60 100% 50%) | oklch(0.968 0.211 109.8) |
#0000FF | hsl(240 100% 50%) | oklch(0.452 0.313 264.1) |
Para o HSL, as duas cores têm a mesma luminosidade. Para o olho, o amarelo é muito mais claro, e o OKLCH reflete isso (0,968 contra 0,452). Na prática, isso significa que você pode montar escalas em que o passo 500 de todas as cores tem contraste parecido, e que mudar só o matiz preserva a legibilidade do texto.
:root {
--blue-100: oklch(0.95 0.03 263);
--blue-300: oklch(0.80 0.10 263);
--blue-500: oklch(0.55 0.21 263);
--blue-700: oklch(0.40 0.17 263);
--blue-900: oklch(0.28 0.10 263);
}
Repare que o croma diminui nas pontas da escala. Cores muito claras ou muito escuras não comportam croma alto dentro do sRGB. Quando um valor sai da gama do monitor, o navegador faz o mapeamento para a cor exibível mais próxima.
color() e gamut amplo (Display P3)
A maioria dos celulares recentes, dos Macs e de muitos monitores exibe mais cores que o sRGB, principalmente verdes e vermelhos saturados. A função color() permite usar esses espaços:
.destaque { color: #FF0000; }
@media (color-gamut: p3) {
.destaque { color: color(display-p3 1 0 0); }
}
Em uma tela P3, color(display-p3 1 0 0) é um vermelho visivelmente mais intenso que o vermelho #FF0000. Em telas sRGB, o navegador reduz a cor para a gama disponível. O oklch() também alcança cores fora do sRGB, basta usar um croma mais alto.
color-mix(): misturas no navegador
color-mix() recebe um espaço de cor e duas cores com proporções opcionais:
:root { --brand: #2563EB; }
.botao:hover {
background: color-mix(in oklch, var(--brand) 85%, black);
}
.fundo-suave {
background: color-mix(in oklch, var(--brand) 12%, white);
}
.borda {
border-color: color-mix(in srgb, var(--brand), transparent 60%);
}
O espaço de mistura muda o resultado. Em srgb, misturar azul e amarelo passa por um cinza apagado no meio; em oklch ou oklab, a transição é mais uniforme e mantém a vivacidade. Para gradientes, a mesma ideia vale com linear-gradient(in oklch, …), que você pode testar no gerador de gradientes. O misturador de cores mostra o resultado de misturas antes de levar para o código.
Sintaxe de cor relativa
A sintaxe relativa parte de uma cor existente e altera só os canais que você quiser. A palavra from decompõe a cor de origem nos canais do espaço escolhido (l, c e h no OKLCH; r, g e b no RGB):
:root { --brand: #2563EB; }
.claro { color: oklch(from var(--brand) calc(l + 0.1) c h); }
.escuro { color: oklch(from var(--brand) calc(l - 0.15) c h); }
.neutro { color: oklch(from var(--brand) l 0.02 h); }
.vidro { background: rgb(from var(--brand) r g b / 20%); }
.oposta { color: oklch(from var(--brand) l c calc(h + 180)); }
Com isso, um design system inteiro pode derivar de uma variável: troque --brand e os tons claros, escuros, neutros e transparentes se atualizam juntos.
light-dark(), currentColor e accent-color
light-dark()
light-dark() recebe duas cores e usa a primeira no esquema claro e a segunda no escuro. Ela só funciona quando color-scheme está definido:
:root { color-scheme: light dark; }
body {
background: light-dark(#FFFFFF, #121212);
color: light-dark(#1F2937, #E5E7EB);
}
Com color-scheme: light dark, o navegador segue a preferência do sistema, e controles nativos e barras de rolagem também se adaptam.
currentColor
currentColor é a cor de texto atual do elemento. Ele existe há muito tempo, mas combina bem com as funções novas: um ícone SVG com fill: currentColor herda a cor do link, e color-mix(in oklch, currentColor 30%, transparent) cria uma borda que acompanha o texto.
accent-color
accent-color aplica a cor da marca em checkboxes, botões de rádio, controles deslizantes e barras de progresso nativos, sem recriar os componentes:
input, progress { accent-color: #2563EB; }
Suporte dos navegadores e fallback com @supports
oklch(), oklab(), color-mix() e a sintaxe de cor relativa já têm amplo suporte nos navegadores modernos e fazem parte do Baseline. light-dark() é mais recente, e navegadores antigos ainda em uso podem não reconhecê-la. Antes de decidir, confira as tabelas de compatibilidade atualizadas no MDN.
Há duas formas simples de garantir um fallback. A primeira aproveita a cascata: o navegador ignora a declaração que não entende e fica com a anterior.
.botao {
background: #2563EB;
background: oklch(0.546 0.215 262.9);
}
A segunda usa @supports, útil quando o bloco inteiro depende do recurso:
:root { --surface: #FFFFFF; }
@supports (color: light-dark(white, black)) {
:root {
color-scheme: light dark;
--surface: light-dark(#FFFFFF, #121212);
}
}
Como migrar uma paleta HEX para OKLCH
A migração não precisa ser um salto único. Um roteiro seguro:
- Inventarie as cores. Liste todos os HEX em uso e agrupe-os em tokens (
--brand,--text,--surface…). Esse passo sozinho já elimina duplicatas quase idênticas. - Converta sem alterar. Use o conversor de cores para obter o equivalente OKLCH de cada HEX. Por exemplo,
#2563EBviraoklch(0.546 0.215 262.9). Mantenha três casas decimais em L e C e uma em H para não haver diferença visível. - Mantenha o HEX como fallback na mesma regra, como mostrado acima, enquanto o público ainda usar navegadores antigos.
- Só depois, reorganize a escala. Com os valores em OKLCH, alinhe a luminosidade dos passos (por exemplo, todos os tons 500 perto de L 0,55) e ajuste o croma nas pontas.
- Verifique o contraste. Uniformidade perceptual não substitui o cálculo da WCAG. Confira os pares de texto e fundo no verificador de contraste.
Cuidado: valores OKLCH arredondados demais (como oklch(0.5 0.2 263)) podem cair um pouco fora do sRGB ou gerar outra cor. Converta com precisão e arredonde só no fim.
Resumo e próximos passos
- Use a sintaxe com espaços e alpha após a barra em
rgb()ehsl(). - Prefira
oklch()para escalas e temas: a luminosidade é consistente entre matizes. - Gere hover, fundos suaves e transparências com
color-mix(in oklch, …)ou com cor relativa. - Adote
light-dark()comcolor-schemee um fallback com@supports. - Migre em etapas: converta, mantenha o HEX de reserva e só depois ajuste a escala.
Para praticar, converta suas cores no conversor, teste misturas no misturador e gradientes em OKLCH no gerador de gradientes. Se os formatos básicos ainda não estão claros, comece pelo guia de códigos de cor, e para montar a paleta completa veja como escolher a paleta de cores de um site.