Bhinish Dhiman
Verificação de contraste no CI: faça o build falhar
Traduzido do original em inglês. Ler o original
Coloque os tokens de cor num teste que confira cada par de texto sobre superfície contra a exigência de contraste dele, e rode esse teste no CI. Assim, uma mudança de token que quebra o contraste faz o build falhar como qualquer outra regressão, em vez de ir para produção e esperar por uma auditoria.
O contraste é verificado quando alguém lembra de verificar. Esse é todo o problema. É uma etapa manual no fim do processo, ela disputa espaço com um prazo, e perde.
A solução é transformá-la numa asserção automatizada, que é como todo outro tipo de regressão é tratado.
Por que a revisão manual falha
Ela acontece tarde demais. O contraste costuma ser revisado perto do fim, quando a cor já sustenta o design e mudá-la significa reabrir decisões já fechadas.
Ela não sobrevive a refatorações. Alguém clareia um cinza em um passo no arquivo de tokens, seis meses depois da auditoria, e nada pega isso.
Ela não acompanha os temas. Claro e escuro dobram a área a cobrir, e o lado escuro é justamente onde o WCAG 2 é menos confiável.
Ninguém reverifica um design system. Um token com contraste insuficiente se espalha por todos os produtos que o consomem.
Cada um desses pontos é a marca de algo que deveria ser um teste.
O que verificar
Não todos os pares possíveis, e sim os pares que você realmente publica. Na maioria dos sistemas, é uma lista curta:
- Texto do corpo sobre o fundo da página
- Texto do corpo sobre um card ou superfície elevada
- Texto atenuado ou secundário sobre os dois
- Cor de link sobre os dois
- Rótulo do botão sobre o fundo do botão, para cada variante
- Cor da borda sobre o fundo ao redor, se a borda identifica um controle
Multiplique pelo número de temas. De doze a vinte asserções cobrem a maioria dos design systems.
Um exemplo completo
import { describe, expect, it } from 'vitest';
import { contrastRatio } from './wcag';
const LIGHT = {
ink: '#10141c',
inkSoft: '#2a313d',
muted: '#5a6473',
paper: '#f7f8fa',
card: '#ffffff',
accent: '#006ad5',
};
const PAIRS = [
{ name: 'body on paper', fg: LIGHT.inkSoft, bg: LIGHT.paper, required: 4.5 },
{ name: 'body on card', fg: LIGHT.inkSoft, bg: LIGHT.card, required: 4.5 },
{ name: 'muted on paper', fg: LIGHT.muted, bg: LIGHT.paper, required: 4.5 },
{ name: 'heading on paper', fg: LIGHT.ink, bg: LIGHT.paper, required: 3 },
{ name: 'button label', fg: '#ffffff', bg: LIGHT.accent, required: 4.5 },
];
describe('design tokens pass contrast', () => {
for (const p of PAIRS) {
it(`${p.name} clears ${p.required}:1`, () => {
const ratio = contrastRatio(p.fg, p.bg);
expect(ratio, `${p.name}: got ${ratio.toFixed(2)}:1`).toBeGreaterThanOrEqual(p.required);
});
}
});
A mensagem de falha importa. expected 4.21 to be greater than or equal to 4.5 manda alguém sair à caça. muted on paper: got 4.21:1 diz exatamente qual token mudar.
Acrescente a asserção APCA para tudo o que é pequeno
O WCAG 2 não enxerga tipografia, então uma razão aprovada num rótulo de 12px não prova nada. Se o que importa para você é a experiência de leitura, e não só o número de conformidade, verifique também o APCA, e cobre limites diferentes de papéis de texto diferentes.
O APCA pede Lc 90 para colunas de texto de corpo e Lc 60 para conteúdo e subtítulos. Aplicar o limite do corpo a um rótulo de 13px o empurraria para quase preto e achataria a sua hierarquia, o que não ajuda ninguém. Verifique o padrão que realmente se aplica a cada papel.
O que encontramos fazendo isso
Acrescentamos isso ao nosso próprio site depois de construir a ferramenta, e esperávamos que passasse. O teste encontrou sete falhas de APCA na interface que passavam no WCAG 2 em todos os casos. A pior era a cor de link do modo escuro, com Lc 37 contra uma exigência de 75: realmente difícil de ler, e invisível para qualquer verificação baseada em razão.
Duas das correções foram tipográficas, e não cromáticas. Os nossos rótulos de 11px não passavam com contraste nenhum, então viraram 13px com peso 500. Os rótulos dos botões não passavam em branco sobre a cor de destaque a 15px, então viraram 17px com peso 600.
Nenhuma dessas é uma correção a que você chegaria ajustando um valor hex.
Calcular a correção em vez de chutar
Quando uma asserção falha, a tentação é escurecer a cor até ela passar. É assim que as paletas vão ficando barrentas com o tempo.
Melhor calcular a mudança mínima: mantenha matiz e croma e procure no eixo da luminosidade o valor mais próximo que passa do limite. Você obtém a menor mudança visual que resolve o problema, e não uma mudança arbitrária, e a cor continua reconhecível.
Geramos todos os valores corrigidos da nossa própria paleta dessa forma, em vez de escolher a olho. Várias das mudanças ficaram abaixo de ΔE 1: imperceptíveis, e ainda assim a diferença entre reprovar e passar.
A parte cultural
O motivo de isso funcionar não é técnico. É que um build que falha é um sinal compartilhado e inequívoco, enquanto uma revisão de acessibilidade é a opinião de uma pessoa disputando com uma data de lançamento.
Quando o contraste vira um teste, ninguém precisa defendê-lo. Ele simplesmente fica vermelho, e o que fica vermelho é corrigido.
O nosso verificador de contraste é a metade interativa disso: quando um teste falha, ele dá a você a cor aprovada mais próxima para colar de volta no arquivo de tokens.