Validação de Contraste WCAG em PHP: Construindo Utilitários de Cores Acessíveis
Introdução
O contraste de cores é um dos problemas de acessibilidade mais comuns na web. Quando usuários podem escolher cores personalizadas para seus perfis, temas ou conteúdo, você precisa validar se suas escolhas permanecem legíveis. Este artigo mostra como implementar dois utilitários PHP que resolvem esse problema: um para verificações rápidas de brilho e outro para validação completa de conformidade WCAG.
Entendendo os Requisitos de Contraste WCAG
As Diretrizes de Acessibilidade para Conteúdo Web (WCAG) definem taxas mínimas de contraste para texto:
| Padrão | Texto Normal | Texto Grande |
|---|---|---|
| WCAG AA | 4.5:1 | 3:1 |
| WCAG AAA | 7:1 | 4.5:1 |
Texto grande é definido como 18pt (24px) ou 14pt (18.66px) em negrito.
TIP
Embora 4.5:1 atenda à conformidade, é o mínimo, não o ideal. Muitos usuários ainda têm dificuldade com o contraste mínimo. Quando possível, busque valores mais altos.
Verificando se uma Cor é Clara ou Escura
Para casos de uso simples—como decidir se deve usar texto branco ou preto em um fundo colorido—você pode usar uma verificação rápida de luminância baseada na fórmula W3C:
public static function isLightColor(string $hex): bool
{
// Remove # se presente
$hex = ltrim($hex, '#');
// Converte hex de 3 dígitos para 6 dígitos
if (strlen($hex) === 3) {
$hex = $hex[0].$hex[0].$hex[1].$hex[1].$hex[2].$hex[2];
}
// Converte hex para RGB
$r = hexdec(substr($hex, 0, 2));
$g = hexdec(substr($hex, 2, 2));
$b = hexdec(substr($hex, 4, 2));
// Calcula luminância usando fórmula W3C
$luminance = (0.299 * $r + 0.587 * $g + 0.114 * $b) / 255;
// Retorna true se a cor for clara
return $luminance > 0.5;
}A fórmula dá mais peso ao verde (0.587) porque os olhos humanos são mais sensíveis à luz verde.
Exemplo de Uso
$corDeFundo = '#3498db';
if (isLightColor($corDeFundo)) {
$corDoTexto = '#000000'; // Usa texto preto em fundos claros
} else {
$corDoTexto = '#ffffff'; // Usa texto branco em fundos escuros
}Validação Completa de Contraste WCAG
Para conformidade adequada com acessibilidade, você precisa do cálculo completo de luminância relativa WCAG. Esta função valida se uma cor tem contraste suficiente contra um fundo branco:
/**
* Valida se uma cor hex tem contraste suficiente com fundo branco
*
* @param string $hexColor A cor hex para validar (com ou sem prefixo #)
* @param float $minContrastRatio A taxa mínima de contraste (4.5 para WCAG AA)
* @return bool True se a cor tem contraste suficiente
*/
public static function hasGoodContrastWithWhite(
string $hexColor,
float $minContrastRatio = 4.5
): bool {
// Converte hex para RGB
$hex = ltrim($hexColor, '#');
// Trata hex de 3 dígitos
if (strlen($hex) === 3) {
$hex = $hex[0].$hex[0].$hex[1].$hex[1].$hex[2].$hex[2];
}
$r = hexdec(substr($hex, 0, 2));
$g = hexdec(substr($hex, 2, 2));
$b = hexdec(substr($hex, 4, 2));
// Calcula luminância relativa (conforme WCAG)
$r = $r / 255;
$g = $g / 255;
$b = $b / 255;
// Lineariza valores sRGB
$r = $r <= 0.03928 ? $r / 12.92 : pow(($r + 0.055) / 1.055, 2.4);
$g = $g <= 0.03928 ? $g / 12.92 : pow(($g + 0.055) / 1.055, 2.4);
$b = $b <= 0.03928 ? $b / 12.92 : pow(($b + 0.055) / 1.055, 2.4);
// Calcula luminância com coeficientes WCAG
$luminance = 0.2126 * $r + 0.7152 * $g + 0.0722 * $b;
// Calcula taxa de contraste contra branco (luminância = 1)
$contrastRatio = ($luminance + 0.05) / (1 + 0.05);
// WCAG AA requer pelo menos 4.5:1 para texto normal
return (1 / $contrastRatio) >= $minContrastRatio;
}Importante
As duas fórmulas de luminância parecem similares, mas servem propósitos diferentes:
- Fórmula W3C (0.299, 0.587, 0.114): Verificação rápida de brilho percebido
- Fórmula WCAG (0.2126, 0.7152, 0.0722): Conformidade precisa de acessibilidade com linearização sRGB
Entendendo a Linearização sRGB
O cálculo WCAG inclui um passo crítico que a fórmula simples pula: linearização sRGB. Monitores não exibem cores linearmente—eles aplicam correção gamma. O limiar de 0.03928 e as fórmulas de transformação convertem de valores de exibição para intensidade real de luz:
// Para valores baixos (cores escuras)
$valorLinear = $valorSrgb / 12.92;
// Para valores mais altos
$valorLinear = pow(($valorSrgb + 0.055) / 1.055, 2.4);Esta linearização é o motivo pelo qual #777777 passa no WCAG AA enquanto #787878 pode falhar, mesmo que pareçam quase idênticos ao olho humano.
Exemplos Práticos de Uso
Validando Cores Escolhidas pelo Usuário
public function updateUserTheme(Request $request): Response
{
$corPrimaria = $request->input('primary_color');
if (!hasGoodContrastWithWhite($corPrimaria)) {
return response()->json([
'error' => 'Esta cor não tem contraste suficiente para texto. Tente um tom mais escuro.'
], 422);
}
// Salva a cor validada
$user->update(['primary_color' => $corPrimaria]);
return response()->json(['success' => true]);
}Suportando Diferentes Níveis WCAG
// WCAG AA para texto normal (padrão)
$passaAA = hasGoodContrastWithWhite($cor);
// WCAG AA para texto grande (14pt negrito ou 18pt)
$passaAAGrande = hasGoodContrastWithWhite($cor, 3.0);
// WCAG AAA para texto normal
$passaAAA = hasGoodContrastWithWhite($cor, 7.0);
// WCAG AAA para texto grande
$passaAAAGrande = hasGoodContrastWithWhite($cor, 4.5);Referência Rápida: Cores Comuns
Veja como cores comuns se comportam contra fundos brancos:
| Cor | Hex | WCAG AA (4.5:1) |
|---|---|---|
| Preto | #000000 | Passa |
| Cinza escuro | #333333 | Passa |
| Cinza médio | #767676 | Passa (mínimo) |
| Cinza claro | #777777 | Falha |
| Azul marinho | #000080 | Passa |
| Azul padrão | #0066CC | Passa |
| Verde brilhante | #00FF00 | Falha |
| Laranja | #FF6600 | Falha |
| Amarelo | #FFFF00 | Falha |
Testando Sua Implementação
describe('hasGoodContrastWithWhite', function () {
it('passa para cores escuras', function () {
expect(hasGoodContrastWithWhite('#000000'))->toBeTrue();
expect(hasGoodContrastWithWhite('#333333'))->toBeTrue();
expect(hasGoodContrastWithWhite('#0066CC'))->toBeTrue();
});
it('falha para cores claras', function () {
expect(hasGoodContrastWithWhite('#FFFFFF'))->toBeFalse();
expect(hasGoodContrastWithWhite('#EEEEEE'))->toBeFalse();
expect(hasGoodContrastWithWhite('#FFFF00'))->toBeFalse();
});
it('trata notação hex de 3 dígitos', function () {
expect(hasGoodContrastWithWhite('#000'))->toBeTrue();
expect(hasGoodContrastWithWhite('#fff'))->toBeFalse();
});
it('respeita taxas de contraste personalizadas', function () {
$cinzaMedio = '#888888';
expect(hasGoodContrastWithWhite($cinzaMedio, 4.5))->toBeFalse();
expect(hasGoodContrastWithWhite($cinzaMedio, 3.0))->toBeTrue();
});
});Conclusão
Construir aplicações acessíveis requer validar escolhas de cores programaticamente. A função isLightColor oferece uma forma rápida de escolher cores de texto contrastantes, enquanto hasGoodContrastWithWhite garante conformidade total com WCAG para cores escolhidas por usuários.
Lembre-se: conformidade com acessibilidade é um requisito legal em muitas jurisdições. A partir de abril de 2026, sites de governos estaduais e locais dos EUA devem atender aos padrões WCAG 2.1 AA, e regulamentações similares existem na UE e outras regiões.

