- Com a função
contrast-color(), o navegador escolhe automaticamente a cor do texto preta ou branca de acordo com diferentes cores de fundo, como em botões
- Mesmo em projetos de grande porte, fica mais fácil manter a legibilidade do texto e aumentar a eficiência da manutenção
- Atualmente, o Safari Technology Preview usa o algoritmo oficial do WCAG 2, mas ele pode divergir da percepção humana real
- A adoção do algoritmo de próxima geração APCA está sendo discutida durante o desenvolvimento do WCAG 3, prometendo uma avaliação de contraste de luminância melhor
- Além do contraste simples entre preto e branco, no futuro devem ser adicionadas várias opções de cor e recursos de melhoria de acessibilidade
Visão geral e contexto da introdução de contrast-color()
- Em designs que usam várias cores de fundo em botões e componentes de interface, a legibilidade da cor da fonte (cor do texto) é importante
- Antes, o desenvolvedor precisava combinar manualmente cor de fundo e cor do texto uma a uma, mas em projetos grandes isso aumentava o risco de complexidade de gerenciamento e ocorrência de erros
- Com a função CSS
contrast-color(), o desenvolvedor só precisa definir a cor de fundo, e o navegador escolhe automaticamente entre texto preto ou branco com maior contraste
- Dessa forma, a eficiência da manutenção e do trabalho de design melhora significativamente
- É possível usar de forma simples com uma declaração como
color: contrast-color(cor);
Exemplo de uso de contrast-color()
- Defina a cor desejada na variável de cor de fundo do botão, e a cor do texto será escolhida automaticamente entre preto ou branco contrastante usando
contrast-color()
- Como só é preciso gerenciar uma cor por vez, a manutenção fica mais fácil ao mudar políticas de design ou dar suporte a modo claro/escuro
button {
background-color: var(--button-color);
color: contrast-color(var(--button-color));
}
- Usando Relative Color Syntax, também é possível gerenciar de forma consistente a cor de fundo e a cor do texto no estado
hover
Considerações de acessibilidade e explicação do algoritmo
- O uso de
contrast-color() não resolve automaticamente todos os problemas de acessibilidade (Accessibility)
- Em certas cores de fundo com brilho intermediário, tanto preto quanto branco podem não atingir o critério necessário
- O algoritmo WCAG 2 usado atualmente no Safari Technology Preview é o padrão oficial de acessibilidade na web
- Esse algoritmo escolhe com base na taxa de contraste, mas às vezes produz resultados que não correspondem ao contraste de luminância percebido visualmente
- Por exemplo, em um fundo azul
#317CFF, mecanicamente o preto é calculado como tendo contraste maior, mas na prática o branco é mais legível
- Em resposta a essas críticas e pedidos de melhoria, está em discussão a adoção do APCA (Accessible Perceptual Contrast Algorithm) no padrão de acessibilidade de próxima geração (WCAG 3)
- Como o APCA calcula o contraste de cor refletindo características da percepção humana, ele garante melhor a legibilidade real
Como oferecer contraste suficiente em ambientes reais
- Com a media query
@media (prefers-contrast: more) do CSS, é possível aplicar estilos adicionais de alto contraste de acordo com as preferências de acessibilidade do usuário
@media (prefers-contrast: more) {
/* definir estilos com contraste mais alto */
}
- Por exemplo, se a cor principal da marca for um verde claro como
#2DAD4E, mesmo que no futuro contrast-color() escolha branco, isso ainda pode não oferecer contraste suficiente para textos pequenos
- Com o algoritmo APCA, é possível consultar em detalhe o contraste mínimo necessário conforme o tamanho e o peso da fonte, o que ajuda em decisões práticas de design
- Na prática, aplicar branco é adequado para texto em 24px com peso 400, mas para fontes mais finas ou textos menores recomenda-se usar uma cor de fundo mais escura
- A equipe de design pode gerenciar facilmente, com variáveis, paletas de cores adequadas para cada condição considerando modo claro/escuro e preferências como
prefers-contrast
--button-color: #2DAD4E;
@media (prefers-contrast: more) {
@media (prefers-color-scheme: light) {
--button-color: #419543;
}
@media (prefers-color-scheme: dark) {
--button-color: #77CA8B;
}
}
button {
background-color: var(--button-color);
color: contrast-color(var(--button-color));
font-size: 1.5rem;
font-weight: 500;
}
- Em essência, com
contrast-color() basta gerenciar as cores com foco na cor de fundo, e o navegador gera automaticamente o par de contraste da cor do texto
Além de preto e branco
- A versão atual de
contrast-color() escolhe apenas entre preto e branco, mas versões iniciais também permitiam escolher entre várias cores
- O CSS Working Group pretende primeiro oferecer a versão simples (escolha apenas entre preto e branco) para manter compatibilidade com futuras mudanças de algoritmo, e no futuro planeja expandir com opções de cores personalizadas e definição do contraste mínimo desejado
- Para necessidades simples, isso já é bastante útil
- A função pode ser usada de várias formas não só para cor de fundo, mas também para bordas e outros elementos visuais
Conclusão e informações de referência
- Depois que o padrão de acessibilidade de próxima geração for refletido,
contrast-color() deverá trocar de algoritmo para oferecer uma seleção automática de contraste melhor
- Até lá, é especialmente útil quando a cor principal de fundo é claramente clara ou escura
- Também pode ser amplamente aplicado a vários elementos de UI, não apenas ao texto
- É recomendável continuar acompanhando algoritmos modernos de acessibilidade como o APCA (Accessible Perceptual Contrast Algorithm)
Materiais de referência
- É possível conferir vários exemplos e critérios de avaliação na documentação oficial do APCA e em APCA Contrast Calculator
- A discussão de padronização da função
contrast-color no CSSWG está em andamento
- Também é possível compartilhar opiniões e participar com feedback no WebKit ou em comunidades relacionadas
1 comentários
Comentários do Hacker News
Estou desenvolvendo uma ferramenta para criar paletas em que os pares de cores tenham razões de contraste WCAG/APCA simples e previsíveis já na fase de design. O site https://www.inclusivecolors.com/ oferece mais recursos no desktop. Uma abordagem é criar amostras de cor por níveis, de 100 (claro) a 900 (escuro), e ajustar a luminosidade para que, por exemplo, a cor de nível 700 tenha contraste claro com a de nível 100, e a de 800 com a de 200. Assim, dá para saber que combinações como red-700 vs gray-100 e green-800 vs yellow-200 certamente oferecem contraste sem precisar checar a luminância. No menu Contrast, também é possível explorar o quanto o algoritmo APCA é mais rigoroso que o WCAG, especialmente com texto escuro sobre fundo claro. É por isso que não se deve usar WCAG para temas escuros. No menu Examples, ao ver casos das paletas Tailwind e IBM Carbon, percebe-se que cada nível varia saturação e matiz (Hue) de forma não linear, então escolher simplesmente entre preto e branco para o melhor contraste é fácil, mas paletas em que branding importa são um problema mais complexo, em que não dá para extrair cores apenas ajustando o brilho.
Há uma forma de fazer algo parecido usando lch
Fonte: https://til.jakelazaroff.com/css/…
LCH também é legal, mas OKLCH é ainda melhor. https://evilmartians.com/chronicles/oklch-in-css-why-quit-rgb-hsl/… Esse artigo mudou completamente minha forma de pensar. É uma ferramenta realmente incrível. Surpreendentemente, nenhum dos meus amigos designers conhecia OKLCH. Essa abordagem resolve muitos problemas.
É a primeira vez que vejo uma função CSS receber parâmetros com esse tipo de callback. É um conceito realmente interessante. Fico curioso se existe alguma outra função nesse estilo além de lch.
Lea Verou escreveu um ótimo texto sobre uma solução alternativa parecida com isso. https://lea.verou.me/blog/2024/contrast-color/
Este artigo é um excelente panorama das vantagens e desvantagens da seleção automática de contraste. Se você está criando um site simples, esse método oferece o contraste correto de forma fácil e direta.
Mas, se for preciso conformidade WCAG em larga escala, eu evitaria isso e usaria uma verdadeira camada de tokens de formatação semântica. Tokens semânticos aceleram o desenvolvimento e podem garantir contrastes visualmente mais agradáveis do que simplesmente alternar entre preto e branco. A grande vantagem de uma camada de tokens semânticos é que criar temas fica muito fácil, então temas claro/escuro passam a ser possíveis com custo quase zero. Se uma cor de marca falhar no WCAG2, você pode criar um tema separado para WCAG2/APCA e obter conformidade ao mesmo tempo em que oferece um contraste melhor.
Eu trabalho com o fluxo de variables/tokens no Figma e também participei da implementação do modo escuro do Figma e da Atlassian. Se alguém tiver dúvidas sobre tokens, temas ou cores de acessibilidade, pode perguntar à vontade.
Fiquei curioso sobre o que exatamente seriam tokens semânticos. Num projeto grande em que trabalhei, usei CSS-in-JS para cálculos relativos de cor e cores de contraste justamente por causa desse tipo de recurso. Espero que essas técnicas sejam amplamente adotadas em breve.
Os últimos 2/3 do conteúdo ficaram prolixos demais e soam pedantes. Em sites/apps de empresas, é arriscado depender desse tipo de função porque a cor resultante é imprevisível. O WebKit pode corrigir um bug e o resultado da cor mudar.
Ainda me custa concordar que a cor de contraste deva ser definida com base no julgamento dos fabricantes de navegadores, e que isso seja sempre correto ou previsível. Fico me perguntando se haverá um critério determinístico para que todos os navegadores produzam o mesmo resultado. Na prática, essa função me parece mais uma ferramenta para apoiar a etapa de design da equipe de UX.
Segundo o artigo, a norma menciona explicitamente o método de cálculo.
A palavra “escolhe” me parece ambígua. Na prática, o algoritmo calcula a cor.
Tirando alguns erros ou pontos confusos na fórmula de exemplo do APCA no link, funciona com 100% de precisão. Se quiser consistência absoluta, quando as duas cores candidatas (a mais clara e a mais escura) forem aceitáveis ao mesmo tempo, basta escolher com base na luminosidade do fundo (L*), por exemplo, se for L* 60 ou mais, escolher a mais clara. Aí se obtém 100% de consistência.
Se em projetos grandes é difícil garantir separadamente que os botões não mudem para cores invisíveis (por exemplo, escuro com texto preto), penso se não bastaria inspecionar manualmente todos os botões antes do lançamento. Ou então definir e compartilhar antecipadamente com toda a equipe que botões escuros nunca podem ter texto preto. Achei interessante a diferença entre contraste cognitivo/perceptivo e contraste matemático. Vou aplicar isso ao meu fluxo de trabalho.
Na prática, até dá para revisar todos os botões manualmente, mas assim o período de testes de regressão antes do lançamento pode levar semanas ou até meses. Em projetos grandes, é fácil haver milhares de botões, e muitos deles só aparecem em combinações específicas de opções ou em determinados fluxos de trabalho.
Se você olhar o APCA, dá para fazer cálculo de contraste baseado em percepção.
Eu fazia estilos de botão quando cores de sistema eram populares. Ficava bonito, mas era impossível saber qual seria a razão de contraste, então alguém calculava isso em JavaScript com getComputedStyle. Se o contraste fosse ruim, usávamos uma segunda cor candidata ou, quando inevitável, reforçávamos o contraste ao redor do texto com text-shadow. Esqueci o método de cálculo, mas parece que daria para comparar a média dos 3 valores RGB. No azul, a média seria menor, então talvez se pudesse dar preferência a texto branco.
No mínimo, seria bom informar recomendações de cores adequadas para pseudoclasses como active, focus, hover, link e visited em temas claro/escuro. O Material UI ainda acrescenta estados como disabled, before e after.
Já fiz um tutorial em vídeo sobre escolher texto preto/branco com base na cor de fundo. Meu método era simples: converter a cor para escala de cinza e então decidir entre preto ou branco. Foi um trabalho divertido. Não sou muito bom em fazer vídeos.
https://youtu.be/tUJvE4xfTgo?si=vFlegFA_7lzijfSR (atenção: em português)
https://news.ycombinator.com/item?id=44015990
O vídeo também parece bom. O código parece interessante, mas como não sei português, não consigo avaliar o conteúdo.
Eu escolho o esquema de cores inteiro manualmente, então me pergunto por que escolher a cor do texto de contraste de um botão seria mais fácil do que simplesmente escolher isso manualmente desde o começo. Esse recurso parece necessário apenas numa situação muito extrema, em que as cores de fundo são escolhidas arbitrariamente e de forma inconsistente, mas ao mesmo tempo não se consegue escolher a cor de primeiro plano (cor do texto do botão). O caso realmente problemático é texto sobre imagens ou fundos variados, em que ele precisa estar sempre visível, e esse recurso não ajuda em nada nisso. Então parece um recurso útil só em situações limitadas, e ainda assim criaram até um novo verbo para isso, enquanto a funcionalidade se resume a escolher entre preto e branco e ainda usa o pior algoritmo de contraste possível (WCAG 2). Impressionante mesmo.
Acho uma pena descartar uma ferramenta só porque você nunca passou por uma situação em que ela fosse útil. Há muitos sites em que o usuário escolhe cores arbitrariamente, ou em que cores são extraídas de conteúdo enviado pelo usuário. Sites que se preocupam com acessibilidade precisam calcular contraste automaticamente nesses casos. Se esse tipo de recurso vier embutido no CSS, até a acessibilidade básica ficará fácil de garantir. Claro que isso não impõe nenhuma limitação a desenvolvedores que queiram criar experiências mais sofisticadas. Seria melhor ainda se houvesse mais personalização, como no pacote npm contrast-color, mas o post do blog diz que o algoritmo que escolhe apenas entre preto e branco foi usado como primeira etapa, com planos de melhorar isso no futuro.
Exemplo: https://coolors.co/8fbfe0-7c77b9-1d8a99-0bc9cd-14fff7
Sobre a crítica de que o algoritmo de seleção de contraste é ruim, o texto deixa claro que ele segue o algoritmo do WCAG 2 e que, quando o WCAG 3 for padronizado, será fácil trocar para esse algoritmo.
Fico curioso se existe alguma alternativa em build time para esse tipo de recurso que possa ser aplicada em SASS, Tailwind etc. Parece que vai levar tempo até isso ser adotado de forma ampla, e também me preocupa se a implementação será igual em todas as plataformas.