Choose from approved foreground candidates when preserving a token set matters. Adjust a color only when creating a new candidate is allowed. In both cases, verify the final serialized color against the required ratio.
Choose between selection and repair
| Constraint | Approach | Important boundary |
|---|---|---|
| Foreground must come from approved tokens | bestOn() |
Winner may still fail |
| Foreground may change but hue identity should remain | adjustLightnessToContrast() |
Search is best effort |
| Opacity may change | requiredAlpha() |
Composite before checking |
| Several palette entries fail one background | Palette contrast fixer | Review roles individually |
Choose the strongest foreground candidate
bestOn() selects the candidate with the highest WCAG ratio against a background:
use PhpColor\Color\Color;
use PhpColor\Color\Contrast\ColorContrast;
use PhpColor\Color\Contrast\ContrastSolver;
$background = Color::parse('#2563eb');
$foreground = ContrastSolver::bestOn(
$background,
[Color::white(), Color::black()],
);
$ratio = ColorContrast::calculate($foreground, $background);
echo $foreground->toHex().PHP_EOL;
printf('%.3f', $ratio);
use PhpColor\Color\Color;
use PhpColor\Color\Contrast\ColorContrast;
use PhpColor\Color\Contrast\ContrastSolver;
$background = Color::parse('#2563eb');
$foreground = ContrastSolver::bestOn(
$background,
[Color::white(), Color::black()],
);
$ratio = ColorContrast::calculate($foreground, $background);
echo $foreground->toHex().PHP_EOL;
printf('%.3f', $ratio);
The output is #ffffff and 5.169 for this background. The method ranks candidates; it does not require the winner to meet AA or AAA. If the candidate array is empty, it returns black.
Adjust lightness toward a target ratio
adjustLightnessToContrast() searches lightness in Oklch while retaining the source chroma, hue, and alpha:
use PhpColor\Color\Color;
use PhpColor\Color\Contrast\ColorContrast;
use PhpColor\Color\Contrast\ContrastSolver;
$foreground = Color::parse('oklch(0.6 0.1 40)');
$background = Color::parse('#fafafa');
$adjusted = ContrastSolver::adjustLightnessToContrast(
$foreground,
$background,
4.5,
);
$passes = ColorContrast::meets(
ColorContrast::calculate($adjusted, $background),
);
use PhpColor\Color\Color;
use PhpColor\Color\Contrast\ColorContrast;
use PhpColor\Color\Contrast\ContrastSolver;
$foreground = Color::parse('oklch(0.6 0.1 40)');
$background = Color::parse('#fafafa');
$adjusted = ContrastSolver::adjustLightnessToContrast(
$foreground,
$background,
4.5,
);
$passes = ColorContrast::meets(
ColorContrast::calculate($adjusted, $background),
);
The returned color is converted back to the input color’s space. The search is best effort: an unreachable target can produce a result that still falls short. It can also create an out-of-gamut Oklch candidate before conversion. Verify the final serialized or rendered color rather than assuming the target was met.
Keep the original design constraint visible
A solver can change the result enough to satisfy a numeric relationship while violating a brand, state, or hierarchy decision. Record which dimensions may change, which token is fixed, and which threshold is required before running it.
Continue with Choose black or white text, Repair text contrast, or ContrastSolver.