Generate semantic theme colors

Derive status colors from a brand chroma and verify an accessible foreground for each role.

PHPColor does not expose a semantic-theme generator. Build the workflow from OKLCH construction and contrast checks when your product needs status roles derived from one brand.

Derive the role colors

Keep recognizable status hues and borrow a safe amount of chroma from the brand:

use PhpColor\Color\Color;

$brand = Color::parse('#7c5cff')->to('oklch');
$brandChroma = $brand->getChannels()['c'];

$roles = [
    'success' => ['hue' => 145, 'maxChroma' => 0.14],
    'warning' => ['hue' => 85, 'maxChroma' => 0.17],
    'error' => ['hue' => 25, 'maxChroma' => 0.19],
    'info' => ['hue' => 250, 'maxChroma' => 0.15],
];

$colors = [];
foreach ($roles as $name => $role) {
    $colors[$name] = Color::oklch(
        0.62,
        min($brandChroma, $role['maxChroma']),
        $role['hue'],
    );
}
use PhpColor\Color\Color; $brand = Color::parse('#7c5cff')->to('oklch'); $brandChroma = $brand->getChannels()['c']; $roles = [ 'success' => ['hue' => 145, 'maxChroma' => 0.14], 'warning' => ['hue' => 85, 'maxChroma' => 0.17], 'error' => ['hue' => 25, 'maxChroma' => 0.19], 'info' => ['hue' => 250, 'maxChroma' => 0.15], ]; $colors = []; foreach ($roles as $name => $role) { $colors[$name] = Color::oklch( 0.62, min($brandChroma, $role['maxChroma']), $role['hue'], ); }

Choose an accessible foreground

Test candidates against the final background rather than assuming white text works:

use PhpColor\Color\Color;
use PhpColor\Color\Contrast\ColorContrast;
use PhpColor\Color\Contrast\WcagLevel;

function foregroundFor($background)
{
    foreach ([Color::black(), Color::white()] as $candidate) {
        if (ColorContrast::meetsFor($candidate, $background, WcagLevel::AA)) {
            return $candidate;
        }
    }

    return null;
}

$onWarning = foregroundFor($colors['warning']);
use PhpColor\Color\Color; use PhpColor\Color\Contrast\ColorContrast; use PhpColor\Color\Contrast\WcagLevel; function foregroundFor($background) { foreach ([Color::black(), Color::white()] as $candidate) { if (ColorContrast::meetsFor($candidate, $background, WcagLevel::AA)) { return $candidate; } } return null; } $onWarning = foregroundFor($colors['warning']);

This recipe composes lower-level capabilities; it is not a guarantee that every generated set is in gamut or visually balanced. Verify the serialized output, contrast, and meaning in the product that uses it. Browse the semantic color collection for worked examples.