Os esquemas de cor são geometria numa roda — e a roda está errada
Publicado a 15/08/2025 · 16 min de leitura · Ferramentas para programadores
Daniel Okonkwo — Programador front-end e redator de Tecnologia na Allin
Desempenho web · Formatos de ficheiro
Verificado a partir de 6 fontes
Um esquema de cor é um ângulo. Pegue no matiz de uma cor base, some um número fixo de graus módulo 360 e tem o esquema: o complementar soma 180°, a tríade soma 120° duas vezes, o análogo soma ±30°, o complementar dividido soma 150° e 210°, o tetrádico soma 90° três vezes. A partir do matiz 210 isso dá 30 para o complementar, 330 e 90 para a tríade, 180 e 240 para os vizinhos, 0 e 60 para o dividido. É todo o cálculo, e qualquer gerador o faz. O problema é o círculo sobre o qual roda. O eixo de matiz do HSL é uma construção geométrica sobre o cubo RGB, não um modelo da visão: ângulos iguais não são passos percetuais iguais, e uma luminosidade HSL igual não é uma claridade percebida igual. Renderizados como hsl(H 100% 50%), o amarelo tem uma luminância relativa WCAG de 0,9278 e o azul 0,0722 — a mesma luminosidade nominal, 12,85 vezes a luz. OKLCH e CIELAB corrigem isto definindo a claridade de forma percetual: o mesmo par é 0,968 contra 0,452 em OKLCH, um rácio de 2,14. A consequência prática é inevitável: a rotação não preserva nada do contraste, por isso uma paleta gerada tem de ser verificada face aos seus fundos antes de sair.
Complementar, tríade, análogo e complementar dividido são apenas rotações: some 180°, 120°, 30° ou 150° a um matiz. A aritmética é trivial. O problema é que o círculo de matizes HSL não é percetualmente uniforme — o amarelo e o azul com a mesma luminosidade HSL diferem 12,85 vezes em luminância — por isso uma paleta gerada tem de ser verificada em contraste depois.
Os quatro esquemas são quatro somas
Toda harmonia de cor com nome é um conjunto de deslocamentos aplicados a um ângulo de matiz. O complementar é base + 180. A tríade é base, base + 120, base + 240. O análogo é base − 30, base, base + 30, embora algumas ferramentas usem 20 ou 45. O complementar dividido é base + 150 e base + 210, ou seja o complementar afastado 30° para cada lado. O tetrádico, também dito quadrado, é base + 90, + 180 e + 270. O monocromático não muda matiz nenhum e varia apenas saturação e luminosidade.
Vejamos um exemplo. Tome um azul médio no matiz 210. O complementar dá (210 + 180) mod 360 = 30, um laranja. A tríade dá 210, (210 + 120) mod 360 = 330 e (210 + 240) mod 360 = 90 — azul, rosa e verde amarelado. O análogo dá 180, 210, 240 — ciano, azul, azul violeta. O complementar dividido dá (210 + 150) mod 360 = 0 e (210 + 210) mod 360 = 60 — vermelho e amarelo. O tetrádico dá 210, 300, 30, 120. O algoritmo não tem mais nada; o módulo é a única subtileza, e existe apenas porque o círculo fecha.
Como os deslocamentos tocam apenas o matiz, a saturação e a luminosidade passam inalteradas. É exatamente isso que dá ao resultado o seu ar de família — e exatamente isso que provoca a falha descrita adiante, porque manter S e L constantes não mantém constante nada do que o olho humano ou um verificador de acessibilidade medem.
Há duas rodas, e não concordam quanto ao azul
Pergunte a um pintor o que está oposto ao azul e a resposta é laranja. Pergunte a um gerador de esquemas e a resposta é amarelo. Ambos têm razão, porque rodam em círculos diferentes. A roda do pintor tem vermelho, amarelo e azul como primárias — o modelo RYB, sistematizado por Itten e ensinado desde então nas escolas de arte — e descreve a mistura subtrativa de pigmentos. A roda do gerador é o eixo de matiz do HSL, uma reparametrização cilíndrica do cubo RGB aditivo, cujas primárias são vermelho, verde e azul e cujas secundárias são ciano, magenta e amarelo.
Os números concretizam a fratura. Projetando o cubo RYB sobre sRGB com a interpolação trilinear clássica, o azul primário RYB cai perto de #2a5f99 e o laranja RYB em #ff8000, e ocupam cantos opostos desse modelo. Na roda HSL, o azul é o matiz 240 e 240 + 180 = 60, que se renderiza como #ffff00, amarelo puro. Nenhuma roda mente. O RYB descreve o que acontece ao misturar tinta; o HSL descreve o que acontece ao misturar luz. São modelos de processos físicos diferentes e nada obriga a que os seus opostos coincidam.
Vale a pena saber isto antes de discutir com o resultado de um gerador. Se uma referência de design lhe diz que o complemento do seu azul de marca é um laranja quente e a ferramenta lhe entrega um amarelo limão, ninguém se enganou — está simplesmente a ler dois sistemas de coordenadas. Se quer a resposta pictórica, alguns geradores oferecem um modo RYB; caso contrário, rode 30° a 40° aquém do complementar matemático e cai na zona laranja.
Uma luminosidade HSL igual não é luz igual
Eis a demonstração. Renderize nove matizes exatamente como hsl(H 100% 50%) — saturação idêntica, luminosidade nominal idêntica — e passe cada um pelo cálculo de luminância relativa da WCAG: divida cada canal por 255, linearize-o e pondere os resultados 0,2126 vermelho, 0,7152 verde, 0,0722 azul. O matiz 60 dá #ffff00 com uma luminância relativa de 0,9278. O matiz 240 dá #0000ff com 0,0722. O mesmo cursor de luminosidade, 12,85 vezes a luz.
As consequências veem-se de imediato em termos de contraste. Sobre fundo branco, esse amarelo faz 1,07:1 — invisível — enquanto esse azul faz 8,59:1 e ultrapassa todos os níveis WCAG. Um contra o outro, o par faz 8,00:1, e é por isso que amarelo sobre azul é uma combinação clássica de sinalética e amarelo sobre branco um erro clássico. A varredura completa é instrutiva: o matiz 0 (#ff0000) tem luminância 0,2126, o matiz 120 (#00ff00) 0,7152 e o matiz 180 (#00ffff) 0,7874. A dispersão num único anel de luminosidade cobre quase todo o intervalo de 0 a 1.
A razão está nos pesos. A luminância relativa é dominada pelo verde, que carrega 71,52 %, enquanto o azul carrega 7,22 %. A luminosidade do HSL não sabe nada disso: é definida como o ponto médio entre o maior e o menor canal RGB, uma grandeza puramente geométrica. Com L = 50 % e S = 100 %, todo o matiz é por construção uma cor cujos canais cobrem todo o intervalo de 0 a 255, e quanta luz isso representa depende inteiramente de quais canais estão acesos.
O que OKLCH e CIELAB entendem por claridade
O CIELAB, publicado pela CIE em 1976, foi a primeira tentativa amplamente adotada de espaço percetualmente uniforme. O seu eixo L* vai de 0 a 100 e assenta numa compressão em raiz cúbica da luminância, escolhida para que passos numéricos iguais pareçam passos iguais. Passe por ele o mesmo amarelo e o mesmo azul: L* = 97,14 para #ffff00 e 32,30 para #0000ff. Continuam muito diferentes, mas agora a diferença é honesta — o amarelo parece mesmo bem mais claro — e o rácio caiu de 12,85 para 3,01.
O OKLab, publicado por Björn Ottosson em 2020 e já parte do CSS Color 4 como oklab() e oklch(), melhora o CIELAB precisamente onde o CIELAB é fraco: a constância de matiz nos azuis e a previsibilidade sob mudanças de claridade. O seu L vai de 0 a 1. O amarelo vale 0,968 e o azul 0,452. A sua forma polar, OKLCH, dá-lhe claridade, croma e matiz — os mesmos três conceitos que o HSL diz oferecer, mas com os dois primeiros a significar o que dizem. Isto importa porque uma paleta gerada a claridade OKLCH fixa parece mesmo uma família de cores com o mesmo brilho, que é exatamente o que os designers julgavam pedir ao HSL.
Vale a pena dizer com clareza uma ressalva. A claridade percetual continua a não ser a luminância relativa, e nem o L do OKLCH nem o L* do CIELAB são o que um verificador WCAG calcula. Duas cores com claridade OKLCH idêntica podem ter luminâncias relativas diferentes, pelo que igualar a claridade de forma percetual não iguala os rácios de contraste. Obtém uma paleta que parece coerente; não obtém uma paleta que passe.
Rotações iguais não são passos iguais
O problema da claridade tem um gémeo no eixo do matiz. Rodar 30° na zona verde — do matiz 90 ao 120 — leva-o entre dois verdes que a maioria das pessoas mal distinguiria. Rodar 30° na zona azul-magenta — de 240 a 270 — leva-o do azul puro a um violeta claramente diferente. Os ângulos são idênticos; as distâncias percebidas não, porque o matiz do HSL deriva das arestas do cubo RGB e o cubo não é uma esfera percetual.
Veja o que isto faz a uma tríade. Rodar 120° a partir do matiz 0 dá #ff0000, #00ff00 e #0000ff, com luminâncias relativas 0,2126, 0,7152 e 0,0722 e claridades OKLCH 0,628, 0,866 e 0,452. Um membro é quase o dobro mais claro do que outro em termos percetuais e dez vezes mais em termos físicos. Rode a mesma tríade a partir do matiz 90 e obtém #80ff00, #0080ff e #ff0080, com claridades OKLCH 0,891, 0,615 e 0,645 — um equilíbrio completamente diferente, da mesma construção, mudando apenas o ângulo de partida.
É por isso que uma tríade às vezes parece equilibrada e às vezes parece uma cor a gritar por cima de outras duas, sem mudar o método. O método é cego a isso. Gerar o mesmo esquema em OKLCH — rodar o matiz, manter L e C — remove quase toda a variância, porque a claridade que mantém é agora a que vê.
A geometria não sabe nada de contraste
Tome um azul de marca real, #2563eb. Em HSL é hsl(221.2 83.2% 53.3%) e sobre branco faz 5,17:1 — folgadamente acima do limiar de 4,5:1 para texto de corpo em WCAG AA. Gere agora a sua família da forma habitual, rodando o matiz com saturação e luminosidade fixas. O parceiro de tríade a +120° é #eb2563 e faz 4,22:1: falha. O complementar dividido a +150° é #eb4a25, 3,79:1: falha. O complementar a +180° é #ebad25, 1,99:1: falha. A +210° é #c6eb25, 1,37:1: falha. A +240°, #63eb25, 1,56:1: falha. Cinco rotações, cinco falhas, a partir de uma base que passa.
Nada correu mal. O gerador fez exatamente o que anuncia: rodou um matiz. O contraste é uma propriedade de um par de cores, e a um gerador de esquemas dá-se uma cor e um ângulo. Não tem fundo com que comparar nem maneira de saber qual das suas saídas acabará como texto e qual como preenchimento decorativo. Esperar uma paleta acessível de uma rotação é esperar resposta a uma pergunta que nunca foi feita.
O processo que resulta tem dois passos, não um. Gere a geometria para encontrar matizes que combinem e depois ajuste a claridade cor a cor até cada uma ultrapassar o rácio de que precisa face à superfície onde vai assentar. Na prática isso significa manter o matiz e o croma que o gerador deu e mexer apenas na claridade — exatamente a operação que o OKLCH torna previsível e o HSL não. Os papéis de texto exigem 4,5:1, o texto grande e os contornos de interface 3:1, e os preenchimentos decorativos nada, pelo que a maioria das paletas acaba com duas variantes de claridade por matiz em vez de uma.
| Matiz | sRGB | Luminância relativa WCAG | Contraste sobre branco | L OKLCH | L* CIELAB |
|---|---|---|---|---|---|
| 60 (amarelo) | #ffff00 | 0,9278 | 1,07:1 | 0,968 | 97,14 |
| 180 (ciano) | #00ffff | 0,7874 | 1,25:1 | 0,905 | 91,11 |
| 120 (verde) | #00ff00 | 0,7152 | 1,37:1 | 0,866 | 87,73 |
| 30 (laranja) | #ff8000 | 0,3670 | 2,52:1 | 0,732 | 67,05 |
| 300 (magenta) | #ff00ff | 0,2848 | 3,14:1 | 0,702 | 60,32 |
| 0 (vermelho) | #ff0000 | 0,2126 | 4,00:1 | 0,628 | 53,24 |
| 270 (violeta) | #8000ff | 0,1181 | 6,25:1 | 0,531 | 40,91 |
| 240 (azul) | #0000ff | 0,0722 | 8,59:1 | 0,452 | 32,30 |
Perguntas frequentes
- Porque é que a minha paleta de tríade parece desequilibrada se a matemática é simétrica?
- Porque a simetria está nos ângulos, não na aparência. Uma tríade roda 120° duas vezes, o que é perfeitamente regular no círculo de matizes, mas esse círculo não é percetualmente regular. A partir do matiz 0 em hsl(H 100% 50%) obtém-se #ff0000, #00ff00 e #0000ff com luminâncias relativas 0,2126, 0,7152 e 0,0722 — o verde emite dez vezes a luz do azul com luminosidade HSL idêntica. Em OKLCH as claridades são 0,628, 0,866 e 0,452, ainda uma dispersão de quase dois para um. O membro que calhar no arco verde ou amarelo dominará, porque esses matizes carregam quase toda a luminância em sRGB. O remédio não é abandonar a tríade mas igualar depois: converta os três para OKLCH, ponha-lhes o mesmo L e volte a renderizar. Os matizes ficam onde a geometria os pôs e o peso visual equilibra-se. Se a sua ferramenta não conseguir, ajustar a luminosidade HSL a olho cor a cor leva-o quase lá — só não pode fazê-lo mantendo um número constante.
- O complemento do azul é laranja ou amarelo?
- Ambos, consoante a roda em que estiver. Na roda RYB dos artistas — vermelho, amarelo e azul como primárias, o modelo que Itten sistematizou e as escolas de arte ainda ensinam — o azul está oposto ao laranja, porque o laranja é o que sai ao misturar as outras duas primárias. Na roda RGB que o HSL expõe, o azul é o matiz 240, o seu oposto é o matiz 60, e o matiz 60 em plena saturação é #ffff00, amarelo puro. Projetar o cubo RYB sobre sRGB coloca o azul RYB perto de #2a5f99 e o laranja RYB em #ff8000, e são mesmo cantos opostos desse modelo. Nenhum está errado. O RYB modela a mistura subtrativa de pigmentos, onde azul e laranja se anulam para um castanho neutro; o RGB modela a mistura aditiva de luz, onde luz azul e luz amarela somam branco. O software trabalha com luz, por isso o complementar de um gerador será o amarelo. Se quer o resultado pictórico, use uma ferramenta com modo RYB ou rode 30° a 40° aquém do complementar matemático — a partir do matiz 240 cai perto do matiz 30, que se renderiza como #ff8000, o laranja que esperava.
- Devo gerar paletas em OKLCH em vez de HSL?
- Para tudo em que os membros devam sentir-se como pares, sim. Manter constante a claridade OKLCH ao longo de uma rotação dá cores que parecem mesmo igualmente claras, ao passo que manter constante a luminosidade HSL dá a dispersão de luminância de 12,85 vezes entre amarelo e azul descrita acima. O OKLCH é suportado em CSS como oklch() e faz parte da especificação CSS Color 4, pelo que pode escrevê-lo diretamente em vez de converter. Duas cautelas práticas. Primeiro, o OKLCH descreve cores fora do gamut sRGB, por isso um croma que funciona num matiz pode ser inalcançável noutro — os navegadores fazem gamut mapping, mas o resultado terá menos croma do que pediu, e uma paleta definida com um único C alto não sairá uniformemente saturada. Mantenha o C conservador, entre 0,10 e 0,15, se precisar que todos os matizes sobrevivam. Segundo, a claridade OKLCH continua a não ser a luminância relativa da WCAG, por isso paletas de L igual não são paletas de contraste igual. Continua a ter de verificar cada cor contra cada fundo que vai encontrar. O que o OKLCH lhe dá é que a verificação passará ou falhará normalmente para todos os membros em conjunto, em vez de um a um por razões que ninguém sabe explicar.
- Uma rotação de matiz maior significa sempre mais contraste?
- Não, e os contraexemplos são fáceis de produzir. O contraste segundo a WCAG depende apenas da luminância relativa, e a rotação de matiz muda a luminância de um modo que nada tem a ver com quanto rodou. A partir de #2563eb, a rotação que menos danifica o contraste é +120° com 4,22:1, e a que mais o danifica é +210° com 1,37:1 — mas +180°, a maior rotação possível, cai em 1,99:1, entre as duas. A relação não é monótona porque os pesos de luminância atingem um pico abrupto no arco verde-amarelo: qualquer rotação que o leve à região dos matizes 60 a 120 subirá a luminância e, sobre fundo branco, baixará o contraste, quer lá tenha chegado rodando 90° ou 250°. O modelo mental útil: a rotação de matiz é um passeio por um relevo cuja altura é a luminância, com uma montanha na zona verde-amarela e um vale na zona azul. A distância percorrida nada diz sobre a altitude atingida. Se duas cores têm de contrastar, afaste deliberadamente as suas claridades; rodar o matiz sozinho não é uma estratégia de contraste, é uma estratégia de matiz.
- De quantas cores precisa realmente um esquema?
- Menos matizes e mais degraus de claridade do que a maioria dos geradores sugere. Uma paleta de interface que funciona costuma ser um matiz de marca, uma rampa neutra e quando muito um acento, cada um existindo em várias variantes de claridade — um tom claro de superfície, um preenchimento médio e uma versão escura apta a texto. Esta última explica o crescimento da conta: como se mostrou acima, um mesmo matiz com a mesma claridade não pode servir ao mesmo tempo de fundo lavado e de texto legível, porque um desses papéis exige 4,5:1 face ao branco e o outro tem de ser mal visível sobre ele. O trabalho do gerador é encontrar as relações de matiz; o seu é dar a cada matiz os dois ou três degraus de claridade que os seus papéis pedem. Um esquema de quatro matizes com uma variante cada falhará algures; um de dois matizes com cinco variantes cada, não. Se está a contar, aponte a uma rampa neutra de nove degraus, cinco degraus do matiz de marca e cinco do acento, e verifique os destinados a texto face a todas as superfícies onde possam aparecer.
Artigos que podem interessar-lhe
Todos os guias →Ferramentas relacionadas
Fontes
- W3C — CSS Color Module Level 4 — hue interpolation, oklab() and oklch(), and the HSL to RGB algorithm
- W3C — Web Content Accessibility Guidelines (WCAG) 2.2 — relative luminance and contrast ratio definitions
- CIE — CIE 015:2018 Colorimetry, 4th Edition — CIELAB and the L* lightness function
- Björn Ottosson — A perceptual color space for image processing (OKLab), 2020
- Charles Poynton — Color FAQ and Gamma FAQ — luminance, lightness and the difference between them
- IEC — IEC 61966-2-1:1999 — Default RGB colour space (sRGB), source of the piecewise transfer function
Detetaste um erro neste artigo?