Skip to content

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ãoTexto NormalTexto Grande
WCAG AA4.5:13:1
WCAG AAA7:14.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:

php
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

php
$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:

php
/**
 * 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:

php
// 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

php
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

php
// 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:

CorHexWCAG AA (4.5:1)
Preto#000000Passa
Cinza escuro#333333Passa
Cinza médio#767676Passa (mínimo)
Cinza claro#777777Falha
Azul marinho#000080Passa
Azul padrão#0066CCPassa
Verde brilhante#00FF00Falha
Laranja#FF6600Falha
Amarelo#FFFF00Falha

Testando Sua Implementação

php
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.