Find accessible foregrounds

Select or adjust foreground colors toward a target contrast ratio.

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.