Set the intended harmony instead of manually guessing replacement hues:
use PhpColor\Color\Palette\ColorPalette;
use PhpColor\Color\Palette\Fixer\HarmonyFixer;
use PhpColor\Color\Palette\Harmony\HarmonyPattern;
$source = ColorPalette::parse([
'primary' => '#776aa6',
'secondary' => '#c8ae66',
'accent' => '#65a6bd',
]);
$fixed = (new HarmonyFixer())->fix($source, [
'target_harmony' => HarmonyPattern::Triadic,
]);
assert([
'primary' => '#776aa6',
'secondary' => '#e19f76',
'accent' => '#68ab97',
] === $fixed->toHex());
use PhpColor\Color\Palette\ColorPalette;
use PhpColor\Color\Palette\Fixer\HarmonyFixer;
use PhpColor\Color\Palette\Harmony\HarmonyPattern;
$source = ColorPalette::parse([
'primary' => '#776aa6',
'secondary' => '#c8ae66',
'accent' => '#65a6bd',
]);
$fixed = (new HarmonyFixer())->fix($source, [
'target_harmony' => HarmonyPattern::Triadic,
]);
assert([
'primary' => '#776aa6',
'secondary' => '#e19f76',
'accent' => '#68ab97',
] === $fixed->toHex());
By default, the fixer anchors the first color and preserves each entry's OKLCH lightness and chroma. It does not check token contrast.