Ir para o conteúdo
Allin

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

Daniel OkonkwoProgramador front-end e redator de Tecnologia na Allin

Desempenho web · Formatos de ficheiro

Verificado a partir de 6 fontes

Ver perfil
Em resumo

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.

Luminância relativa WCAG
Nove matizes com luminosidade HSL (50 %) e saturação (100 %) idênticas, medidos de três formas
MatizsRGBLuminância relativa WCAGContraste sobre brancoL OKLCHL* CIELAB
60 (amarelo)#ffff000,92781,07:10,96897,14
180 (ciano)#00ffff0,78741,25:10,90591,11
120 (verde)#00ff000,71521,37:10,86687,73
30 (laranja)#ff80000,36702,52:10,73267,05
300 (magenta)#ff00ff0,28483,14:10,70260,32
0 (vermelho)#ff00000,21264,00:10,62853,24
270 (violeta)#8000ff0,11816,25:10,53140,91
240 (azul)#0000ff0,07228,59:10,45232,30
Gerador de esquemas de corDerive esquemas complementar, análogo, triádico, tetrádico, dividido e monocromático de uma cor base.Experimentar a ferramenta

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
ExplicaçãoGradientes, bandas e porque o meio parece turvoInterpolar em sRGB faz a média de números codificados em gama: o ponto médio de vermelho para verde é #808000 quando a resposta a meia-luz é #bcbc00 — menos 57,2 % de luz. As bandas são outro problema aritmético: 8 bits dão 256 degraus, e um gradiente escuro pode ter apenas 28. Eis ambos, calculados.ExplicaçãoRácio de contraste: como a WCAG o calcula realmenteO rácio WCAG é (L1 + 0,05) ÷ (L2 + 0,05), e L é a luminância relativa, não o brilho. O verde carrega 71,52 % e o azul 7,22 %, razão pela qual o azul puro sobre branco passa com 8,59:1 enquanto o cinzento médio falha com 3,95:1. Eis o cálculo inteiro, corrido de ponta a ponta.TutorialHEX vs RGB: como ler e converter coresHEX e RGB são duas notações para as mesmas cores. Eis como ler cada uma, converter entre elas e adicionar transparência.GuiaO que um minificador pode remover e o que não pode tocarA minificação tem de preservar o significado, e o interessante são os espaços que carregam significado: o combinador descendente, os espaços dentro de calc(), o intervalo entre dois elementos em linha. Medido aqui em ficheiros reais, incluindo o que o brotli já lhe ia poupar.ExplicaçãoComo funcionam de verdade as fontes do Instagram (não são fontes)Nada é estilizado. Cada letra é trocada por outro carácter Unicode que por acaso se parece com negrito ou cursivo — daí um leitor de ecrã ler uma algaraviada e alguns aparelhos mostrarem quadrados vazios.ExplicaçãoO que uma percentagem de disponibilidade permite realmenteTrês noves soa a promessa até se dividir em minutos. O que 99,9 % compra por ano, por mês, por semana e por dia; porque é que a janela de medição conta muito mais do que o nove a mais; e os dois meses diferentes que esta ferramenta usa para o mesmo identificador.

Ferramentas relacionadas

Fontes

Detetaste um erro neste artigo?