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.