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 */

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:

CorHSLOKLCH
#FFFF00hsl(60 100% 50%)oklch(0.968 0.211 109.8)
#0000FFhsl(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:

  1. 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.
  2. Converta sem alterar. Use o conversor de cores para obter o equivalente OKLCH de cada HEX. Por exemplo, #2563EB vira oklch(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.
  3. Mantenha o HEX como fallback na mesma regra, como mostrado acima, enquanto o público ainda usar navegadores antigos.
  4. 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.
  5. 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

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.