Ciência das cores para designers de interfaces: o que aprendi construindo um site sem cores
As Diretrizes de Acessibilidade para Conteúdo Web (WCAG) do W3C exigem uma taxa de contraste mínima de 4,5:1 para texto normal, mas uma pesquisa da WebAIM de 2023 descobriu que 83,6% dos um milhão de sites mais acessados apresentavam falhas detectáveis de contraste WCAG 2 em suas páginas iniciais. Eu construí o blakecrosley.com com o problema oposto: contraste máximo (21:1) em todo lugar, depois reduzindo-o seletivamente.1
Resumo
Projetei meu site pessoal com zero cores de marca. Toda a hierarquia visual opera através de texto branco sobre preto absoluto (#000000) em quatro níveis de opacidade: 100%, 65%, 40% e 10%. Essa decisão me forçou a aprender ciência perceptual das cores — por que o sRGB mente sobre espaçamento uniforme, como o OKLCH resolve isso e por que o modo escuro exige relações de contraste diferentes do modo claro. As ferramentas interativas abaixo permitem que você explore taxas de contraste e diferenças entre espaços de cores. A conclusão: entender a ciência por trás da percepção de cores produz decisões de design melhores do que a intuição estética.
Minha não-paleta de cores
A maioria dos design systems começa com uma paleta de cores. O meu começa com a ausência de uma:
:root {
--color-bg-dark: #000000;
--color-bg-elevated: #111111;
--color-bg-surface: #1a1a1a;
--color-text-primary: #ffffff;
--color-text-secondary: rgba(255,255,255,0.65);
--color-text-tertiary: rgba(255,255,255,0.4);
--color-border: rgba(255,255,255,0.1);
--color-border-subtle: rgba(255,255,255,0.05);
--color-accent: #ffffff;
}
Dez tokens. Nenhuma cor de marca. Nenhuma paleta semântica de erro/sucesso/aviso. Toda a hierarquia visual opera através de quatro níveis de transparência.2
Por que quatro níveis funcionam
Cada nível serve a uma função de comunicação específica:
| Nível | Opacidade | Token CSS | Função | Taxa WCAG (sobre #000) |
|---|---|---|---|---|
| Primário | 100% | --color-text-primary |
Títulos, texto do corpo, conteúdo crítico | 21:1 (AAA) |
| Secundário | 65% | --color-text-secondary |
Subtítulos, navegação, metadados | 13,7:1 (AAA) |
| Terciário | 40% | --color-text-tertiary |
Timestamps, texto auxiliar, estados desabilitados | 8,4:1 (AAA) |
| Estrutural | 10% | --color-border |
Bordas, divisores, separação de fundo | N/A (não-texto) |
Todos os níveis passam no WCAG AAA (7:1) para contraste de texto. O nível terciário a 40% de opacidade produz uma taxa de 8,4:1 — quase o dobro do mínimo AA de 4,5:1. A escolha brutalista do preto absoluto (#000000 em vez de #0a0a0a ou #1a1a1a) dá a cada nível de texto a margem máxima de contraste.3
O incidente do --spacing-2xs
Descobri o valor de design tokens rigorosos quando usei --spacing-2xs para a margem de um subtítulo. O token não existia na minha definição :root. O CSS falhou silenciosamente, o layout quebrou e eu passei 20 minutos depurando um problema de espaçamento que deveria ter sido um erro em tempo de compilação. A correção: mudar para --spacing-xs (o menor token que eu defini). A lição: se um valor não existe no sistema, o design está errado, não o sistema.4
Por que o sRGB mente para designers
O problema da não-uniformidade perceptual
O sRGB (o espaço de cores padrão para a web) mapeia cores em um cubo onde cada eixo (vermelho, verde, azul) varia de 0 a 255. Mover 50 unidades no canal vermelho não produz a mesma mudança percebida que mover 50 unidades no canal verde. Os olhos humanos contêm mais cones sensíveis ao verde do que ao vermelho ou azul, tornando variações de verde mais perceptíveis do que variações equivalentes de vermelho.5
A consequência prática: um designer que cria uma paleta de cores espaçando valores hexadecimais uniformemente produz cores que parecem espaçadas de forma desigual. O cinza “médio” entre #000000 e #FFFFFF não é #808080 (ponto médio matemático), mas aproximadamente #777777 (ponto médio perceptual), porque a percepção humana de brilho segue uma lei de potência em vez de uma função linear.6
Meu site contorna o problema inteiramente. Ao usar apenas branco em opacidades variáveis, evito a armadilha de uniformidade do sRGB. Escalas de opacidade se comportam linearmente com a transparência percebida contra um fundo preto — uma propriedade que a mistura de cores sRGB não compartilha.
A solução OKLCH
OKLCH (Oklab Lightness, Chroma, Hue) é um espaço de cores perceptualmente uniforme onde distâncias matemáticas iguais correspondem a diferenças percebidas iguais. Um passo de 10 unidades de luminosidade sempre parece a mesma quantidade de mudança, independentemente da cor inicial.7
/* sRGB: mathematically even, perceptually uneven */
--gray-100: #f5f5f5;
--gray-200: #e5e5e5;
--gray-300: #d4d4d4;
/* OKLCH: perceptually even steps */
--gray-100: oklch(96% 0 0);
--gray-200: oklch(88% 0 0);
--gray-300: oklch(80% 0 0);
O CSS moderno suporta oklch() nativamente. Para meu próximo projeto que precise de uma paleta de cores, definirei a paleta em OKLCH. Para meu site atual, o sistema baseado em opacidade alcança a mesma uniformidade perceptual por meios diferentes.
Minha decisão sobre modo escuro: sem modo claro
Meu site não tem media query prefers-color-scheme. Ele opera exclusivamente em modo escuro. A decisão foi deliberada.8
O argumento a favor de dois modos: os usuários esperam uma opção de modo claro. A integração com preferências do sistema respeita a escolha do usuário.
O argumento contra (que eu escolhi): manter dois sistemas visuais inevitavelmente compromete ambos. Um design que funciona a 65% de opacidade sobre preto requer uma opacidade diferente sobre branco (mais perto de 45%) para alcançar o mesmo peso perceptual. Cada estado de interação, cada borda, cada sombra precisa de recalibração. Eu escolhi projetar um sistema bem feito em vez de dois sistemas medianos.
O fundo preto absoluto (#000000) maximiza o contraste disponível para cada nível de texto:
/* My actual typography contrast hierarchy */
.hero__title { color: var(--color-text-primary); } /* 21:1 */
.hero__subtitle { color: var(--color-text-secondary); } /* 13.7:1 */
.nav a { color: var(--color-text-secondary); } /* 13.7:1 */
.nav a:hover { color: var(--color-text-primary); } /* 21:1 */
A transição do estado hover (secundário → primário) fornece feedback funcional inteiramente através da mudança de contraste — nenhuma mudança de cor necessária.
Contraste e legibilidade
Requisitos de contraste WCAG
| Nível | Texto normal (< 18pt) | Texto grande (≥ 18pt ou 14pt negrito) |
|---|---|---|
| AA | 4,5:1 | 3:1 |
| AAA | 7:1 | 4,5:1 |
A taxa de contraste mede a diferença de luminância relativa entre as cores do primeiro plano e do fundo. Uma taxa de 1:1 significa nenhum contraste (cores idênticas). Uma taxa de 21:1 significa contraste máximo (preto sobre branco ou branco sobre preto).9
Além do WCAG: APCA
O algoritmo de contraste do WCAG 2 tem limitações conhecidas. O algoritmo trata todas as cores igualmente, independentemente da polaridade (escuro sobre claro vs. claro sobre escuro), apesar de pesquisas mostrarem que a percepção humana de contraste difere significativamente entre os dois modos.10
O APCA (Accessible Perceptual Contrast Algorithm) aborda essas limitações ao considerar: - Sensibilidade de polaridade: texto escuro sobre fundos claros é mais legível do que texto claro sobre fundos escuros na mesma taxa matemática de contraste - Frequência espacial: fontes finas requerem mais contraste do que fontes em negrito no mesmo tamanho - Adaptação: o olho se adapta à luminância da página ao redor, afetando o contraste percebido
Espera-se que o APCA forme a base dos requisitos de contraste do WCAG 3.0. Meu site se beneficia da descoberta sobre polaridade: como uso exclusivamente claro sobre escuro, preciso de taxas de contraste mais altas do que um site em modo claro. Meu nível de texto mais baixo (40% de opacidade, taxa de 8,4:1) excede até mesmo o mínimo recomendado pelo APCA para texto de corpo em fundos escuros.
Cor semântica sem cor
Sistemas de cores de produção normalmente atribuem cores a funções (verde para sucesso, vermelho para erro). Meu site evita cor funcional inteiramente porque não possui UI transacional — sem formulários, sem mensagens de status, sem estados de sucesso/erro. O conteúdo é estático.
Se eu precisasse de cor semântica, a adicionaria cirurgicamente:
| Função | Abordagem típica | Minha abordagem hipotética |
|---|---|---|
| Sucesso | Verde | Texto branco + ícone de check |
| Erro | Vermelho | Texto branco + ícone X + ênfase de borda |
| Aviso | Âmbar | Texto branco + ícone de exclamação |
Combinar ícones com texto elimina a comunicação exclusivamente por cor que falha para aproximadamente 8% dos homens com deficiência na visão de cores. A abordagem também preserva meu sistema monocromático. A cor serviria como acento, não como elemento estrutural.11
A hierarquia tipografia-primeiro
Sem cor carregando a hierarquia, a tipografia carrega tudo no meu site:
:root {
--font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text",
"SF Pro Display", "Helvetica Neue", Arial, sans-serif;
--font-size-display: 5rem; /* 80px — hero headlines */
--font-size-7xl: 3.875rem; /* 62px */
--font-size-base: 1rem; /* 16px — body text */
--font-size-xs: 0.75rem; /* 12px — metadata */
}
Fontes do sistema, não web fonts personalizadas. A escolha é tanto uma decisão brutalista (usar o material nativo da plataforma) quanto uma decisão de performance (zero latência de carregamento de fontes, contribuindo para pontuação 100/100 no Lighthouse). O tamanho display (80px) com espaçamento entre letras apertado (-0.03em) dá aos títulos gravidade sem decoração. O texto de corpo a 16px com altura de linha generosa (1.7) prioriza legibilidade sobre densidade.12
A escala tipográfica de 13 passos, de 0,75rem a 5rem, fornece granularidade suficiente para expressar hierarquia apenas pelo tamanho. Combinada com os quatro níveis de opacidade, tenho 52 combinações possíveis (13 tamanhos × 4 opacidades) — mais do que suficiente para expressar qualquer hierarquia de conteúdo sem recorrer à cor.
Principais conclusões
Para designers: - Defina paletas de cores em OKLCH em vez de sRGB; espaços de cores perceptualmente uniformes produzem hierarquia previsível e taxas de contraste consistentes em toda a paleta - Teste seu nível de texto terciário contra o WCAG AAA (7:1), não apenas o AA (4,5:1); o limiar AAA fornece margem suficiente para condições reais de tela (brilho baixo, reflexos, displays envelhecidos) - Considere se seu projeto realmente precisa de cor; meu site prova que tipografia e opacidade sozinhas podem sustentar uma hierarquia visual completa
Para desenvolvedores:
- Use oklch() no CSS para definições de cores e teste taxas de contraste tanto com WCAG 2 (padrão atual) quanto com APCA (padrão futuro); o suporte a oklch() nos navegadores está disponível em todos os navegadores modernos
- Implemente o modo escuro ajustando luminosidade e saturação no espaço OKLCH em vez de inverter valores hexadecimais; o ajuste perceptual produz resultados melhores do que a inversão matemática
- A aplicação rigorosa de design tokens previne falhas silenciosas no CSS; se um token não existe, o design deve mudar, não o sistema de tokens
Referências
-
WebAIM, “The WebAIM Million: 2023 Accessibility Analysis,” 2023. ↩
-
Author’s CSS custom properties from
critical.css. 10 color tokens, all derived from white-on-black opacity relationships. ↩ -
Author’s WCAG contrast calculations. Primary (21:1), secondary (13.7:1), tertiary (8.4:1), all exceeding AAA 7:1 minimum. ↩
-
Author’s debugging experience.
--spacing-2xswas used but never defined in:root. Documented in MEMORY.md error entries. ↩ -
Hunt, R.W.G., The Reproduction of Colour, Wiley, 2004. ↩
-
Poynton, Charles, Digital Video and HD, Morgan Kaufmann, 2012. Gamma correction and perceptual linearity. ↩
-
Ottosson, Bjorn, “A perceptual color space for image processing,” 2020. OKLCH specification. ↩
-
Author’s design decision. Single dark mode avoids visual compromise inherent in maintaining parallel light/dark systems. ↩
-
W3C, “Web Content Accessibility Guidelines (WCAG) 2.1,” 2018. ↩
-
Somers, Andrew, “APCA Contrast Calculator,” 2023. ↩
-
W3C, “WCAG 2.1 Success Criterion 1.4.1: Use of Color,” 2018. ↩
-
Author’s typography system. 13-step font scale from 0.75rem (12px) to 5rem (80px). System font stack eliminates FOIT/FOUT. ↩