Audit status colors across vision profiles

Test the semantic color pairs that must remain distinct under every PHPColor vision simulation profile.

Test the status relationships users must distinguish, then use labels, icons, and placement as redundant cues. Passing a simulation check does not make hue-only status UI accessible.

Define the semantic pairs

This test names the important relationships instead of comparing every token with every other token:

use PhpColor\Color\Palette\ColorPalette;
use PhpColor\Color\Vision\ColorVisionSimulator;
use PhpColor\Color\Vision\VisionProfile;

$statuses = ColorPalette::parse([
    'success' => '#15803d',
    'warning' => '#a16207',
    'danger' => '#b91c1c',
    'info' => '#1d4ed8',
]);

$pairs = [
    ['success', 'warning'], ['success', 'danger'],
    ['warning', 'danger'], ['danger', 'info'],
];
use PhpColor\Color\Palette\ColorPalette; use PhpColor\Color\Vision\ColorVisionSimulator; use PhpColor\Color\Vision\VisionProfile; $statuses = ColorPalette::parse([ 'success' => '#15803d', 'warning' => '#a16207', 'danger' => '#b91c1c', 'info' => '#1d4ed8', ]); $pairs = [ ['success', 'warning'], ['success', 'danger'], ['warning', 'danger'], ['danger', 'info'], ];

Record failures across profiles

Keep the profile, pair, and project threshold in the output:

$simulator = new ColorVisionSimulator();
$failures = [];

foreach (VisionProfile::cases() as $profile) {
    foreach ($pairs as [$first, $second]) {
        if (!$simulator->areDistinguishable(
            $statuses->get($first),
            $statuses->get($second),
            $profile,
            threshold: 0.08,
        )) {
            $failures[] = [
                'profile' => $profile->value,
                'first' => $first,
                'second' => $second,
            ];
        }
    }
}

assert([
    ['profile' => 'deuteranopia', 'first' => 'warning', 'second' => 'danger'],
    ['profile' => 'monochromacy', 'first' => 'danger', 'second' => 'info'],
] === $failures);
$simulator = new ColorVisionSimulator(); $failures = []; foreach (VisionProfile::cases() as $profile) { foreach ($pairs as [$first, $second]) { if (!$simulator->areDistinguishable( $statuses->get($first), $statuses->get($second), $profile, threshold: 0.08, )) { $failures[] = [ 'profile' => $profile->value, 'first' => $first, 'second' => $second, ]; } } } assert([ ['profile' => 'deuteranopia', 'first' => 'warning', 'second' => 'danger'], ['profile' => 'monochromacy', 'first' => 'danger', 'second' => 'info'], ] === $failures);

This fixture exposes two risky relationships. After revising an application palette, make the production assertion require an empty failure list. The threshold belongs to PHPColor's simulated sRGB-distance heuristic, not WCAG or a medical diagnosis. Review failures in their components and test contrast separately. Continue with simulate color vision deficiencies, check color distinguishability, or simulate a palette for deuteranopia.