Ir para o seletor de cores

CSS color-mix() e cores relativas: tons, hover e transparência

O color-mix() mistura duas cores num espaço de cor que você escolhe, e a sintaxe de cores relativas permite editar um canal de uma cor existente, como em oklch(from var(--brand) calc(l - 0.1) c h). Os dois rodam no navegador, funcionam com variáveis CSS e substituem a maioria das funções de cor do Sass. Misture em oklab para tons e hover, em srgb para transparência.

7 min de leitura

Como o color-mix() funciona

O color-mix() recebe um espaço de cor e duas cores com porcentagens opcionais e devolve a cor entre elas. color-mix(in oklab, var(--brand) 90%, black) significa 90% da cor da marca e 10% de preto, misturados em Oklab. Com uma porcentagem só, a outra cor fica com o resto. Sem nenhuma, cada uma fica com 50%.

O navegador calcula o resultado em tempo de execução, então funciona com custom properties, currentColor e troca de tema. As funções do Sass rodam uma vez, no build, e não enxergam valores de var().

Se as duas porcentagens somarem menos de 100%, o resultado também fica parcialmente transparente: color-mix(in srgb, red 30%, blue 30%) é uma mistura meio a meio com 60% de opacidade. Mantenha a soma em 100%, a não ser que você queira esse efeito.

CSS

.button {
    --brand: #2563eb;
    background: var(--brand);
}

.button:hover {
    background: color-mix(in oklab, var(--brand) 90%, black);
}

.badge {
    background: color-mix(in oklab, var(--brand) 10%, white);
    border-color: color-mix(in srgb, var(--brand) 30%, transparent);
}

Em que espaço de cor misturar: srgb, oklab ou oklch

O espaço muda o resultado mais do que a maioria das pessoas imagina. Abaixo, o azul puro #0000ff misturado meio a meio com o amarelo puro #ffff00, calculado com a mesma matemática que o navegador usa.

Em srgb os canais dão média exata de 128, 128, 128: a mistura é #808080, um cinza com croma zero. O Oklab também mistura em linha reta, e o azul e o amarelo ficam em lados opostos dela, então eles se anulam quase por completo num azul acinzentado e apagado. O Oklch interpola o ângulo do matiz, pelo caminho mais curto de 264° a 110°, então o ponto médio é um azul-petróleo vivo, com matiz 187°, que mantém o croma das duas cores.

EspaçoAzul + amareloOKLCH L, CBlue-600 + yellow-400OKLCH L, C
srgb#8080800.600, 0#9098800.665, 0.035
oklab#6cabc70.710, 0.077#90a3ad0.703, 0.026
oklch#00bdb00.710, 0.262#00bd9e0.703, 0.194

O segundo par é #2563eb e #facc15 do Tailwind. Os resultados em oklch ficam fora do sRGB, então o croma aparece antes do gamut mapping e o hex é a cor mais próxima que uma tela sRGB consegue mostrar.

Duas coisas chamam a atenção. O srgb chega à luminosidade 0,600, enquanto o ponto médio perceptual entre o azul (0,452) e o amarelo (0,968) é 0,710: é daí que vem o meio escuro e barrento dos gradientes em srgb. E só o oklch mantém a mistura viva, ao custo de passar por um matiz que nenhuma das duas cores tem.

Regra prática: misture em oklab para tons claros, tons escuros e estados de hover, em que você fica num só matiz e quer passos de luminosidade uniformes. Misture em oklch quando as duas cores forem saturadas e você quiser um resultado saturado. Misture em srgb para transparência, porque é assim que o navegador compõe as camadas.

Receitas: hover, tons claros e variantes transparentes

Todos os valores abaixo usam #2563eb como --brand. Texto branco sobre ela tem razão de contraste de 5,17:1.

Misturar 10% de preto escurece mais em oklab do que em srgb: o texto branco sobe para 6,50:1, contra 6,11:1. Oklab e oklch dão o mesmo resultado quando a segunda cor é preto ou branco, porque elas não têm matiz para interpolar.

ReceitaCSSResultadoContraste
Hover, 10% mais escurocolor-mix(in oklab, var(--brand) 90%, black)#1f55cc6.50:1 com texto branco
Active, 20% mais escurocolor-mix(in oklab, var(--brand) 80%, black)#1947ae8.22:1 com texto branco
Hover misturado em srgbcolor-mix(in srgb, var(--brand) 90%, black)#2159d36.11:1 com texto branco
Fundo em tom claro, 10%color-mix(in oklab, var(--brand) 10%, white)#e8f0ff4.51:1 com texto na cor da marca
Fundo em tom claro, 20%color-mix(in oklab, var(--brand) 20%, white)#d2e2ff3.95:1 com texto na cor da marca
Transparente, 20%color-mix(in srgb, var(--brand) 20%, transparent)rgba(37, 99, 235, 0.2)Parece #d3e0fb sobre branco

Cuidado com os tons claros. O texto na cor da marca tem 5,17:1 sobre branco, 4,51:1 sobre um tom claro de 10% e só 3,95:1 sobre um de 20%, o que reprova no WCAG AA para texto normal. Se você colocar texto na cor da marca sobre um tom claro dela, use o tom de hover no texto.

Misturar com transparent mantém a cor e reduz só o alpha, porque o navegador mistura valores pré-multiplicados. O resultado se adapta ao que está por trás: a mesma regra parece #d3e0fb sobre branco e #132651 sobre uma superfície escura #0f172a. Um tom claro misturado com branco continua pálido numa página escura, então, para componentes que aparecem nos dois temas, a versão transparente costuma ser a melhor escolha.

Sintaxe de cores relativas: edite um canal

A sintaxe de cores relativas pega uma cor existente, separa em canais e deixa você reescrever qualquer um deles com calc(). Em oklch(from var(--brand) l c h), as palavras-chave l, c e h guardam a luminosidade (de 0 a 1), o croma e o matiz de --brand.

Ao contrário do color-mix(), ela muda exatamente uma propriedade: baixa a luminosidade sem mexer no croma, gira o matiz sem mudar a luminosidade ou adiciona alpha a uma cor guardada em hex.

ReceitaCSSResultado a partir de #2563ebTexto branco
Mais escuraoklch(from var(--brand) calc(l - 0.1) c h)#0442c88.02:1
Mais claraoklch(from var(--brand) calc(l + 0.1) c h)#4484ff3.51:1
Metade do cromaoklch(from var(--brand) l calc(c * 0.5) h)#4e6faf4.98:1
Matiz +30°oklch(from var(--brand) l c calc(h + 30))#7a49df5.46:1
Complementaroklch(from var(--brand) l c calc(h + 180))#9567004.98:1
Cinza, mesma luminosidadeoklch(from var(--brand) l 0 h)#7070704.95:1
Alpha de 50%oklch(from var(--brand) l c h / 0.5)rgba(37, 99, 235, 0.5)Depende do fundo

CSS

:root {
    --brand: #2563eb;
    --brand-hover: oklch(from var(--brand) calc(l - 0.08) c h);
    --brand-muted: oklch(from var(--brand) l calc(c * 0.5) h);
    --brand-ring: oklch(from var(--brand) l c h / 0.4);
    --accent: oklch(from var(--brand) l c calc(h + 30));
}

Observe as quatro linhas que mantêm o l: mudar o croma ou o matiz move o contraste com texto branco só entre 4,95:1 e 5,46:1, contra 5,17:1 da cor original. É a luminosidade que define o contraste, então variações de matiz feitas assim mantêm mais ou menos a mesma acessibilidade. As cores mais clara e complementar ficam fora do sRGB com croma total, e o navegador as converte na cor mais próxima que consegue mostrar.

color-mix(), cores relativas e darken() do Sass

Use color-mix() quando você pensa em “um pouco de preto” ou “um pouco de transparência”. Use a sintaxe de cores relativas quando você pensa em canais: “a mesma cor, 0,1 mais escura”. As duas substituem a maioria das funções de cor do Sass e acompanham a troca de tema sem novo build.

color-mix()Sintaxe de cores relativasSass darken()
Onde rodaNo navegadorNo navegadorNo build
Funciona com var() e currentColorSimSimNão
Modelo de corO espaço que você indicar: srgb, oklab, oklch, hsl e outrosQualquer função de cor: oklch(), rgb(), hsl(), lab()Só HSL
Ideal paraTons claros e escuros, transparência, mistura de dois tokensMudar um canal: luminosidade, croma, matiz ou alphaCódigo antigo com cores fixas
Tamanho do passoUma fração da distância até a segunda corExato, nas unidades do canalPontos HSL fixos, desiguais para o olho
Suportado desdeChrome 111, Safari 16.2, Firefox 113Chrome 119, Safari 18, Firefox 128Qualquer navegador, a saída é estática

Por que o darken() do Sass dá passos desiguais

darken($color, 10%) subtrai 10 pontos da luminosidade HSL. A luminosidade HSL não é a luminosidade percebida, então a mudança visível depende do matiz. Veja darken(10%) ao lado de oklch(from … calc(l - 0.1) c h) em quatro cores comuns de interface, medido com ΔE OK, em que cerca de 0,02 é a menor diferença que dá para perceber.

CorSass darken(10%)ΔE OKOKLCH l − 0.1ΔE OK
#2563eb#134cca0.078#0442c80.099
#facc15#d7ad050.099#d9ab000.101
#16a34a#1076360.136#00842b0.094
#dc2626#b21d1d0.090#b700000.089

O mesmo darken(10%) muda o verde 1,7 vez mais que o azul. A versão OKLCH fica entre 0,089 e 0,101 para qualquer matiz; a pequena variação vem do gamut mapping, já que algumas cores mais escuras caem logo fora do sRGB. O próprio Sass recomenda color.adjust() ou color.scale() em vez de darken(), e em CSS puro você não precisa de nenhum dos dois.

Suporte nos navegadores e fallbacks

O color-mix() funciona no Chrome e no Edge 111, no Safari 16.2 e no Firefox 113, todos lançados em 2023. A sintaxe de cores relativas chegou no Chrome e no Edge 119 e no Firefox 128; o Safari lançou uma versão inicial no 16.4 e a sintaxe atual no Safari 18. Todos os navegadores atuais suportam as duas.

O truque comum de colocar um valor simples na linha anterior não funciona quando o novo valor contém var(). Uma declaração com var() é sempre aceita na análise, então o navegador descarta a sua linha de fallback e, se não conseguir calcular o color-mix(), volta para o valor herdado ou inicial. Em vez disso, coloque a versão moderna dentro de @supports.

CSS

.button:hover {
    background: #1f55cc;
}

@supports (color: color-mix(in oklab, red, blue)) {
    .button:hover {
        background: color-mix(in oklab, var(--brand) 90%, black);
    }
}

@supports (color: oklch(from red l c h)) {
    .badge {
        border-color: oklch(from var(--brand) l c h / 0.3);
    }
}

Perguntas e respostas

O que o color-mix() faz no CSS?

O color-mix() mistura duas cores num espaço de cor que você indica e devolve o resultado, por exemplo color-mix(in oklab, var(--brand) 90%, black) para uma cor 10% mais escura que a da marca. Ele roda no navegador, então funciona com variáveis CSS e currentColor.

Que espaço de cor usar no color-mix()?

Use oklab para tons claros, tons escuros e estados de hover, porque ele dá passos de luminosidade uniformes e mantém o matiz. Use oklch ao misturar duas cores saturadas quando quiser um resultado saturado, e srgb ao misturar com transparent. Misturar azul e amarelo em srgb dá o cinza chapado #808080, enquanto o oklch dá um azul-petróleo vivo.

Como escurecer uma cor no hover só com CSS?

Use background: color-mix(in oklab, var(--brand) 90%, black) ou background: oklch(from var(--brand) calc(l - 0.1) c h). Para #2563eb, o primeiro dá #1f55cc e o segundo #0442c8, elevando o contraste com texto branco de 5,17:1 para 6,50:1 e 8,02:1.

Como deixar transparente a cor de uma variável CSS?

Escreva color-mix(in srgb, var(--brand) 20%, transparent) ou oklch(from var(--brand) l c h / 0.2). Os dois funcionam quando --brand está guardada em hex e mantêm a cor intacta, reduzindo só a opacidade.

O color-mix() é suportado em todos os navegadores?

Sim, todos os navegadores atuais suportam: Chrome e Edge desde o 111, Safari desde o 16.2 e Firefox desde o 113. Para navegadores antigos, coloque primeiro uma cor estática e a versão com color-mix() dentro de @supports, porque uma linha com var() não consegue cair no fallback sozinha.

Qual a diferença entre color-mix() e a sintaxe de cores relativas?

O color-mix() mistura duas cores por porcentagem, o que serve para tons claros, tons escuros e transparência. A sintaxe de cores relativas, como oklch(from var(--brand) l c calc(h + 30)), reescreve canais individuais de uma cor, o que serve para passos exatos de luminosidade, mudanças de matiz e alpha.

Experimente no PickShade

Guias relacionados