Adjust lightness, chroma, and hue

Choose the perceptual channel that matches the visual relationship you want to change.

Change one perceptual channel when the desired relationship can be named precisely: lightness for brighter or darker, chroma for more or less colorful, and hue for movement around the color wheel. Use mixing, tints, or shades when the goal is a relationship to another endpoint instead.

Match the operation to the visual intent

Intent Channel or operation What stays conceptually fixed
Make a color brighter or darker lighten() or darken() Oklch chroma and hue
Make a color more or less colorful saturate() or desaturate() Oklch lightness and hue
Move to another hue family rotateHue() Oklch lightness and chroma
Move toward white or black tint() or shade() Nothing is held fixed; colors are mixed
Move between two arbitrary colors Color::mix() The chosen interpolation model

These methods convert through Oklch internally, then return the receiver's concrete color type. The source object remains unchanged.

Compare the three channels

use PhpColor\Color\Color;

$base = Color::parse('#3b82f6');

echo $base->lighten(0.08)->toHex().PHP_EOL;
echo $base->saturate(0.04)->toHex().PHP_EOL;
echo $base->rotateHue(30)->toHex().PHP_EOL;
use PhpColor\Color\Color; $base = Color::parse('#3b82f6'); echo $base->lighten(0.08)->toHex().PHP_EOL; echo $base->saturate(0.04)->toHex().PHP_EOL; echo $base->rotateHue(30)->toHex().PHP_EOL;

The output is:

#549cff
#207dff
#886cee
#549cff #207dff #886cee

The three results differ because each operation changes a different coordinate, not because one method is a stronger version of another.

Use relative changes for families, exact channels for targets

lighten() adds to Oklch lightness; darken() subtracts from it:

use PhpColor\Color\Color;

$base = Color::parse('#3b82f6');
$hover = $base->lighten(0.08);
$pressed = $base->darken(0.08);

echo $hover->toHex();
echo $pressed->toHex();
use PhpColor\Color\Color; $base = Color::parse('#3b82f6'); $hover = $base->lighten(0.08); $pressed = $base->darken(0.08); echo $hover->toHex(); echo $pressed->toHex();

Lightness is clamped to 0..1. A negative amount reverses lighten(), and darken($amount) is implemented as lighten(-$amount). Use these methods for relative changes. Convert to Oklch and use withChannel('l', ...) when you require an exact lightness.

Adjust chroma and hue

saturate() changes Oklch chroma, despite its familiar name:

use PhpColor\Color\Color;

$base = Color::parse('#3b82f6');
$vivid = $base->saturate(0.04);
$muted = $base->desaturate(0.08);
$shifted = $base->rotateHue(30);
use PhpColor\Color\Color; $base = Color::parse('#3b82f6'); $vivid = $base->saturate(0.04); $muted = $base->desaturate(0.08); $shifted = $base->rotateHue(30);

saturate() adds the supplied amount and prevents chroma from falling below zero. desaturate() always reduces chroma by the absolute value of its argument. rotateHue() accepts positive or negative degrees and normalizes the result into 0 <= h < 360.

High chroma values can fall outside the gamut of a target RGB space. Inspect the converted output rather than assuming that lightness and chroma survive an RGB round trip unchanged.

Relative channel changes do not guarantee equal contrast steps, semantic state relationships, or in-gamut delivery. For a fixed target, convert to Oklch and use withChannel(); for a reusable progression, build a scale and verify its serialized values.

Continue with Create tints and shades, Derive button states, or Lightness, Chroma, and Hue for visible proofs.