Create new colors based on another color's channels and values.


![Adam Argyle](https://web.dev/images/authors/argyle.jpg) Adam Argyle [X](https://twitter.com/argyleink) [GitHub](https://github.com/argyleink) [Bluesky](https://bsky.app/profile/nerdy.dev)

<br />

In Chrome 119 is a very powerful color feature from [CSS Color Level 5](https://www.w3.org/TR/css-color-5/#relative-colors). Relative color syntax
creates a smooth path for color manipulation within CSS, offering ways for
authors and designers to:

- [Lighten](https://developer.chrome.com/blog/css-relative-color-syntax#lighten_a_color)
- [Darken](https://developer.chrome.com/blog/css-relative-color-syntax#darken_a_color)
- [Saturate](https://developer.chrome.com/blog/css-relative-color-syntax#saturate_a_color)
- [Desaturate](https://developer.chrome.com/blog/css-relative-color-syntax#desaturate_a_color)
- [Chroma boost](https://developer.chrome.com/blog/css-relative-color-syntax#chroma_boost_a_color)
- [Adjust opacity](https://developer.chrome.com/blog/css-relative-color-syntax#adjust_opacity_a_color)
- [Invert](https://developer.chrome.com/blog/css-relative-color-syntax#invert_a_color)
- [Complement](https://developer.chrome.com/blog/css-relative-color-syntax#complement_a_color)
- [Convert](https://developer.chrome.com/blog/css-relative-color-syntax#color_conversion)
- [Contrast](https://developer.chrome.com/blog/css-relative-color-syntax#contrast_a_color)
- [Color palettes](https://developer.chrome.com/blog/css-relative-color-syntax#color_palettes)
  - [Monochromatic](https://developer.chrome.com/blog/css-relative-color-syntax#monochromatic_palettes)
  - [Analogous](https://developer.chrome.com/blog/css-relative-color-syntax#analogous_palettes)
  - [Triadic](https://developer.chrome.com/blog/css-relative-color-syntax#triadic_palettes)
  - [Tetradic](https://developer.chrome.com/blog/css-relative-color-syntax#tetradic_palettes)
  - [Monochromatic with hue rotation](https://developer.chrome.com/blog/css-relative-color-syntax#monochromatic_with_a_slight_hue_rotation)

**Before** relative color syntax, to modify the opacity of a color, you need to
create custom properties for the channels of a color, usually HSL, and assemble
them into a final color and final variant color. This means managing a lot of
color pieces, which can quickly become burdensome.

    :root {
      --brand-hue: 300deg;
      --brand-saturation: 75%;
      --brand-lightness: 50%;

      --brand-hsl:
        var(--brand-hue)
        var(--brand-saturation)
        var(--brand-lightness);

      --brand-color: hsl(var(--brand-hsl));

      /* all this work just so I can set the opacity to 50% in a variant */
      --brand-color-variant: hsl(var(--brand-hsl) / 50%);
    }

**After** relative color syntax, you can create a brand color with any color space
or syntax you need, and create a half opacity variant with much less code. It's
also much easier to read the intent of the styles and system.

    :root {
      --brand-color: hsl(300deg 75% 50%);
      --brand-color-variant: hsl(from var(--brand-color) h s l / 50%);
    }

This post will help you [learn the syntax](https://developer.chrome.com/blog/css-relative-color-syntax#syntax_overview) and [demonstrate common color manipulations](https://developer.chrome.com/blog/css-relative-color-syntax#use_cases_and_demonstrations).

If you prefer video, nearly all of the following article is covered in this GUI Challenge.

[Video](https://www.youtube.com/watch?v=1z_ViBjdnSw)

## Syntax overview

The goal of relative color syntax is to allow deriving a color from another
color. The base color is called the originating color, this is the color that
comes after the new [`from`](https://developer.chrome.com/blog/css-relative-color-syntax#the_from_keyword) keyword. The browser will
[convert](https://developer.chrome.com/blog/css-relative-color-syntax#color_conversion) and break this originating color apart and offer
the parts as variables for use in the new color definition.

![A
diagram of the syntax rgb(from green r g b / alpha) is shown, with an arrow
leaving the top of green and arching into the rgb beginning of the function,
this arrow splits into 4 arrows that then point to their relevant variable. The
4 arrows are red, green, blue and alpha. Red and blue have a value of 0, green
is 128 and alpha is 100%.](https://developer.chrome.com/static/blog/css-relative-color-syntax/image/a-diagram-the-syntax-rgb-3bffe6c436572.png)

The preceding diagram shows the originating color `green` being converted to the
new color's [color space](https://developer.chrome.com/docs/css-ui/high-definition-css-color-guide#a_review_of_the_classic_color_spaces),
turned into individual numbers represented as `r`, `g`, `b`, and `alpha`
variables, which are then directly used as a new `rgb()` color's values.

While this image shows the breakdown, process and variables, it's also not
changing the color. The variables are put back into the color unchanged, thus
resulting in a green color still.

> [!NOTE]
> **Note:** For a review on specifying colors, visit the [high definition color
> guide](https://developer.chrome.com/docs/css-ui/high-definition-css-color-guide) for all the information you need about web color. This post assumes familiarity with functional color notation, color spaces like OKLCH and color gamuts.

### The `from` keyword

The first part of the syntax to learn is the part `from <color>` addition to
specifying a color. It goes right before you specify the values. Here's a code
example where all that has been added is `from green`, right before the values
for `rgb()` are specified.

    .syntax-introduction_same-colors {
      color: green;
      color: rgb(0 128 0);
      color: rgb(from green r g b);    /* result = rgb(0 128 0) */
    }

That `from` keyword, when seen as the first parameter in functional notation,
turns the color definition into a relative color! After the `from` keyword, CSS
expects a color, **a color that will inspire the next color**.

### Color conversion

In simpler terms, it converts green to r g and b channels for use in a new
color.

    rgb(from green r g b)           /* r=0 g=128 b=0 */
    rgb(from rgb(0 128 0) r g b);   /* r=0 g=128 b=0 */

### Colors from custom properties

Reading `rgb from green` is very clear and easy to understand. This is why
custom properties and relative color syntax make such a great match, because you
can take the mystery out of the `from` color. You also don't generally need to
know the color format of the custom property color, as you're creating a new
color in a format of your choice.

    rgb(from rgb(255 105 180) r g b) /* ????? */
    rgb(from var(--hotpink) r g b)   /* clear */

### Work in your preferred color space

You can choose the color space with your choice of functional color notation.

    rgb(from hsl(120 100% 25%) r g b)     /*  r=0   g=128  b=0    */
    hsl(from hsl(120 100% 25%) h s l)     /*  h=120 s=100% l=25%  */
    hwb(from hsl(120 100% 25%) h w b)     /*  h=120 w=0%   b=50%  */
    lch(from hsl(120 100% 25%) l c h)     /*  l=46  c=68   h=134  */

The relative color syntax has that conversion step; the color after `from` is
converted into the color space as specified at the beginning of the relative
color. The input and output don't need to match, which is very liberating.

The ability to choose a color space is also empowering, as choosing a color
space tends to be more focused around the type of color alternation than it is a
preference. The preference is in the results, not the color format or channel
types. This will become much clearer in the sections demonstrating use cases, as
different color spaces excel at different tasks.

### Mix, match, omit and repeat the variables

Something strange but exciting about this syntax, the variables don't have to be
put back in order and can be repeated.

    rgb(from green g g g)    /* rgb(128 128 128) */
    rgb(from green b r g)    /* rgb(0 0 128) */
    rgb(from green 0 0 g)    /* rgb(0 0 128) */

### Opacity as a variable

The syntax also provides the opacity as a variable named `alpha`. It's optional,
and goes after the `/` in the functional color notation.

    rgb(from #00800080 r g b / alpha)             /* alpha=50% */
    rgb(from rgba(0,128,0,.5) r g b / alpha)      /* alpha=50% */
    rgb(from rgb(0 128 0 / 50%) r g b / alpha)    /* alpha=50% */

### Use calc() or other CSS functions on the variables

So far we've been creating the color green over and over again. Learning the
syntax, getting familiar with the conversion and destructuring steps. Now is
time to modify the variables, alter the output so it's not the same as the
input.

    green                              /*  h=120 s=100% l=25%  */
    hsl(from green calc(h * 2) s l)    /*  h=240 s=100% l=25%  */

It's navy now! The hue was doubled, taking a hue of `120` and turning it into
`240`, completely altering the color. This rotated the hue along the color
wheel, a neat trick made very simple with [cylindrical color spaces](https://en.wikipedia.org/wiki/Color_model#Cylindrical-coordinate_color_models)
like [HSL](https://developer.chrome.com/docs/css-ui/high-definition-css-color-guide#hsl),
[HWB](https://developer.chrome.com/docs/css-ui/high-definition-css-color-guide#hwb),
[LCH](https://developer.chrome.com/docs/css-ui/high-definition-css-color-guide#lch), and
[OKLCH](https://developer.chrome.com/docs/css-ui/high-definition-css-color-guide#oklch).

To visually see the values of channels, so you can get the math just right without guessing or having the spec memorized, try this [relative color syntax channel values](https://preset-env.cssdb.org/blog/relative-color-syntax-channel-values/) tool. It reveals each channels value based on the syntax you specify, allowing you to know exactly what values you have available to play with.

### Check for browser support

    @supports (color: rgb(from white r g b)) {
      /* safe to use relative color syntax */
    }

## Use cases and demonstrations

The following examples and use cases have many alternative syntaxes to achieve
similar or the same results. The variations come from the color spaces and the
channels they offer.

Also, many examples will show color adjustments with the verbiage of `by` and
`to`. A color changed `by` is a relative color change; a change that uses the
value of the variable and makes an adjustment based on its current value. A
color changed `to` is an absolute color change; a change that does not use the
value of the variable and instead specifies a completely new value.

All demo's can be found in this [Codepen collection](https://codepen.io/collection/eJPQjM).

### Lighten a color

The OKLCH, [OKLAB](https://developer.chrome.com/docs/css-ui/high-definition-css-color-guide#oklab),
[XYZ](https://developer.chrome.com/docs/css-ui/high-definition-css-color-guide#xyz_xyz_d50_xyz_d65) or
[sRGB](https://developer.chrome.com/docs/css-ui/high-definition-css-color-guide) color spaces provide the most
predictable results when lightening colors.

> [!NOTE]
> **Note:** Learn more about predictable results for gradients in this [episode](https://www.youtube.com/watch?v=w_vk1j8aYmU) of the CSS podcast.

#### Lighten by an amount

The following example `.lighten-by-25` takes the color `blue` and converts it to
OKLCH, then lightens the blue by increasing the `l` (lightness) channel by multiplying
the current value by `1.25`. This pushes the blue lightness towards white by 25%.

    .lighten-by-25 {
      background: oklch(from blue calc(l * 1.25) c h);
    }

<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/BaMpJod?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen BaMpJod by web-dot-dev on Codepen">See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/embed/BaMpJod"&amp;amp;gt;Pen BaMpJod by web-dot-dev on Codepen&amp;amp;lt;/a&amp;amp;gt;</iframe>

#### Lighten to a specific value

The following example `.lighten-to-75` doesn't utilize the `l` channel to
lighten `blue`, it instead completely replaces the value with `75%`.

    .lighten-to-75 {
      background: oklch(from blue 75% c h);
    }

<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/bGOzVNP?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen bGOzVNP by web-dot-dev on Codepen">See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/embed/bGOzVNP"&amp;amp;gt;Pen bGOzVNP by web-dot-dev on Codepen&amp;amp;lt;/a&amp;amp;gt;</iframe>

### Darken a color

The same color spaces effective at lightening a color, are also great for
darkening color.

#### Darken by an amount

The following example `.darken-by-25` takes the color blue and converts it to
OKLCH, then darkens the blue by decreasing the `l` (lightness) channel by 25% by
multiplying the value by `.75`. This pushes the blue color towards black by 25%.

    .darken-by-25 {
      background: oklch(from blue calc(l * .75) c h);
    }

<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/ZEwLvbV?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen ZEwLvbV by web-dot-dev on Codepen">See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/embed/ZEwLvbV"&amp;amp;gt;Pen ZEwLvbV by web-dot-dev on Codepen&amp;amp;lt;/a&amp;amp;gt;</iframe>

#### Darken to a specified value

The following example `.darken-to-25` doesn't utilize the `l` channel to darken
`blue`, it instead completely replaces the value with `25%`.

    .darken-to-25 {
      background: oklch(from blue 25% c h);
    }

<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/RwEvWWe?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen RwEvWWe by web-dot-dev on Codepen">See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/embed/RwEvWWe"&amp;amp;gt;Pen RwEvWWe by web-dot-dev on Codepen&amp;amp;lt;/a&amp;amp;gt;</iframe>

### Saturate a color

#### Saturate by an amount

The following example `.saturate-by-50` uses the `s` from `hsl()` to increase
`orchid`'s vibrance by a relative `50%`.

    .saturate-by-50 {
      background: hsl(from orchid h calc(s * 1.5) l);
    }

<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/GRzryoW?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen GRzryoW by web-dot-dev on Codepen">See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/embed/GRzryoW"&amp;amp;gt;Pen GRzryoW by web-dot-dev on Codepen&amp;amp;lt;/a&amp;amp;gt;</iframe>

#### Saturate to a specific amount

The following example `.saturate-to-100` doesn't utilize the `s` channel from
`hsl()`, it instead specifies a desired saturation value. In this example,
saturation is raised to `100%`.

    .saturate-to-100 {
      background: hsl(from orchid h 100% l);
    }

<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/xxmMwVP?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen xxmMwVP by web-dot-dev on Codepen">See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/embed/xxmMwVP"&amp;amp;gt;Pen xxmMwVP by web-dot-dev on Codepen&amp;amp;lt;/a&amp;amp;gt;</iframe>

### Desaturate a color

#### Desaturate by an amount

The following example `.desaturate-by-half` uses the `s` from `hsl()` to decrease
the saturation of `indigo` by half.

    .desaturate-by-half {
      background: hsl(from indigo h calc(s / 2) l);
    }

<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/OJdWzNj?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen OJdWzNj by web-dot-dev on Codepen">See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/embed/OJdWzNj"&amp;amp;gt;Pen OJdWzNj by web-dot-dev on Codepen&amp;amp;lt;/a&amp;amp;gt;</iframe>

#### Desaturate to a specific value

Rather than desaturate by an amount, you can desaturate to a specific desired
value. The following example `.desaturate-to-25` creates a new color based on
`indigo` but sets the saturation to 25%.

    .desaturate-to-25 {
      background: hsl(from indigo h 25% l);
    }

<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/KKbJdgV?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen KKbJdgV by web-dot-dev on Codepen">See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/embed/KKbJdgV"&amp;amp;gt;Pen KKbJdgV by web-dot-dev on Codepen&amp;amp;lt;/a&amp;amp;gt;</iframe>

### Chroma boost a color

This effect is similar to saturating a color but is different in a couple of
ways. Firstly, it's a `chroma` change and not a `saturation` change, and this is
because the color spaces that can boost into high dynamic range don't use
saturation. The color spaces which feature `chroma` are high dynamic range
capable, allowing authors to boost color vibrance further than saturation is
even capable of.

    .increase-chroma {
      background: oklch(from orange l calc(c + .1) h);
    }

<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/MWZLabj?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen MWZLabj by web-dot-dev on Codepen">See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/embed/MWZLabj"&amp;amp;gt;Pen MWZLabj by web-dot-dev on Codepen&amp;amp;lt;/a&amp;amp;gt;</iframe>

### Adjust opacity a color

Making a semi-transparent variant of a color is one of the most common color
adjustments done in design systems. See the example in the introduction of this
article if you missed it, it outlines the problem space really well.

#### Adjust opacity by an amount

    .decrease-opacity-by-25 {
      background: rgb(from lime r g b / calc(alpha / 2));
    }

<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/eYxgyZx?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen eYxgyZx by web-dot-dev on Codepen">See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/embed/eYxgyZx"&amp;amp;gt;Pen eYxgyZx by web-dot-dev on Codepen&amp;amp;lt;/a&amp;amp;gt;</iframe>

#### Adjust opacity to a specific value

    .decrease-opacity-to-25 {
      background: rgb(from lime r g b / 25%);
    }

<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/ZEVwbeL?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen ZEVwbeL by web-dot-dev on Codepen">See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/embed/ZEVwbeL"&amp;amp;gt;Pen ZEVwbeL by web-dot-dev on Codepen&amp;amp;lt;/a&amp;amp;gt;</iframe>

### Invert a color

Color inversion is a common color adjustment function found in color libraries.
One way to accomplish this is to convert a color to RGB then subtract each
channel's value from 1.

    .invert-each-rgb-channel {
      background: rgb(from yellow calc(255 - r) calc(255 - g) calc(255 - b));
    }

<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/QWPpdEX?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen QWPpdEX by web-dot-dev on Codepen">See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/embed/QWPpdEX"&amp;amp;gt;Pen QWPpdEX by web-dot-dev on Codepen&amp;amp;lt;/a&amp;amp;gt;</iframe>

### Complement a color

If your goal wasn't to invert a color but rather complement it, then hue
rotation is likely what you're looking for. Pick a color space which offers the
hue as an angle, then use `calc()` to rotate the hue by an amount you want.
Finding a color's complement is done by rotating by half a turn, in this case
you can add or subtract from the `h` channel by `180` to achieve the result.

    .complementary-color {
      background: hsl(from blue calc(h + 180) s l);
    }

<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/bGOzVRN?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen bGOzVRN by web-dot-dev on Codepen">See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/embed/bGOzVRN"&amp;amp;gt;Pen bGOzVRN by web-dot-dev on Codepen&amp;amp;lt;/a&amp;amp;gt;</iframe>

### Contrast a color

As a method of achieving accessible color contrast ratios, consider L\* (Lstar).
This uses the (approximately) perceptually uniform lightness (L) channel from
LCH and OKLCH, in a `calc()`. Depending if you are targeting low, medium or high
contrast, the L\* delta is around \~40, \~50, or \~60.
<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/PoVWEGK?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen PoVWEGK by web-dot-dev on Codepen">See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/embed/PoVWEGK"&amp;amp;gt;Pen PoVWEGK by web-dot-dev on Codepen&amp;amp;lt;/a&amp;amp;gt;</iframe>

This technique works well across any hue in LCH or OKLCH.

#### Contrast a darker color

The `.well-contrasting-darker-color` class demonstrates L\* with a delta of 60.
Since the originating color is a dark color (low value lightness), 60% (.6) is added
to the lightness channel. This technique is used to find a well contrasting,
same hue, dark text color on a light background.

    .well-contrasting-darker-color {
      background: darkred;
      color: oklch(from darkred calc(l + .60) c h);
    }

<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/poYbOZa?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen poYbOZa by web-dot-dev on Codepen">See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/embed/poYbOZa"&amp;amp;gt;Pen poYbOZa by web-dot-dev on Codepen&amp;amp;lt;/a&amp;amp;gt;</iframe>

#### Contrast a lighter color

The `.well-contrasting-lighter-color` class demonstrates L\* with a delta of 60%
also. Since the originating color is a light color (high value lightness), .60 is
subtracted from the lightness channel.

    .well-contrasting-lighter-color {
      background: lightpink;
      color: oklch(from lightpink calc(l - .60) c h);
    }

<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/KKEMxxZ?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen KKEMxxZ by web-dot-dev on Codepen">See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/embed/KKEMxxZ"&amp;amp;gt;Pen KKEMxxZ by web-dot-dev on Codepen&amp;amp;lt;/a&amp;amp;gt;</iframe>

### Color palettes

Relative color syntax is very good at creating color palettes. It's especially
useful and powerful due to the number of color spaces available. The following
examples all use OKLCH because the lightness channel is reliable and the hue
channel can be rotated without side effects. The final example demonstrates a
combination of lightness and hue rotation adjustments for a more interesting
result!

Open the example source code for these and try changing the `--base-color`, to
see just how dynamic these palettes are. It's fun!

If you like video, I give in depth information about [building color palettes in CSS with OKLCH on YouTube](https://www.youtube.com/watch?v=6aCsAMgwnjE).

[Video](https://www.youtube.com/watch?v=6aCsAMgwnjE)

#### Monochromatic palettes

To create a monochromatic palette is to make a palette all from the same hue but
with variations in lightness and darkness. The middle color is the source color
for the palette, where two lighter and two darker variants are put on either
side.

    :root {
      --base-color: deeppink;

      --color-0: oklch(from var(--base-color) calc(l + .20) c h); /* lightest */
      --color-1: oklch(from var(--base-color) calc(l + .10) c h);
      --color-2: var(--base-color);
      --color-3: oklch(from var(--base-color) calc(l - .10) c h);
      --color-4: oklch(from var(--base-color) calc(l - .20) c h); /* darkest */
    }

<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/oNVLPPK?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen oNVLPPK by web-dot-dev on Codepen">See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/embed/oNVLPPK"&amp;amp;gt;Pen oNVLPPK by web-dot-dev on Codepen&amp;amp;lt;/a&amp;amp;gt;</iframe>

##### Try out a bunch of palettes made with relative color syntax and OKLCH

[Open Props](https://open-props.style), a library of free CSS variables, offers
color palettes built with this strategy and makes them easily usable with an
import. They're also all built off of a color you can customize, you just give
it a color and it spits out a palette!
<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/yLGZYpg?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen yLGZYpg by web-dot-dev on Codepen">See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/embed/yLGZYpg"&amp;amp;gt;Pen yLGZYpg by web-dot-dev on Codepen&amp;amp;lt;/a&amp;amp;gt;</iframe>

#### Analogous palettes

Since hue rotation is so easy with OKLCH and HSL, it's trivial to create an
[analogous color palette](https://en.wikipedia.org/wiki/Analogous_colors).
Rotate the hue by an amount you like the results of and change the base color,
and watch new palettes get built by the browser.

    :root {
      --base-color: blue;

      --primary:   var(--base-color);
      --secondary: oklch(from var(--base-color) l c calc(h - 45));
      --tertiary:  oklch(from var(--base-color) l c calc(h + 45));
    }

<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/ZEVwbry?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen ZEVwbry by web-dot-dev on Codepen">See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/embed/ZEVwbry"&amp;amp;gt;Pen ZEVwbry by web-dot-dev on Codepen&amp;amp;lt;/a&amp;amp;gt;</iframe>

#### Triadic palettes

Similar to complementary colors, [triadic color palettes](https://www.studiobinder.com/blog/what-is-a-triadic-color-scheme-definition/)
are opposing but harmonious hue rotations given a base color. Where a
complementary color is on the opposite side of a color, like a straight line
drawn through the middle of the color wheel, triadic palettes are like a
triangle of lines, finding 2 colors equally rotated from a base color.
Accomplish this by rotating the hue `120deg`.

This is a slight simplification of the color theory, but it's enough to kick
start you into the more complex triadic palettes if you're interested.

    :root {
      --base-color: yellow;
      --triad-1: oklch(from var(--base-color) l c calc(h - 120));
      --triad-2: oklch(from var(--base-color) l c calc(h + 120));
    }

<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/KKbJdoN?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen KKbJdoN by web-dot-dev on Codepen">See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/embed/KKbJdoN"&amp;amp;gt;Pen KKbJdoN by web-dot-dev on Codepen&amp;amp;lt;/a&amp;amp;gt;</iframe>

#### Tetradic palettes

Tetradic palettes are four colors evenly divided around the color wheel, making
a palette with no clear dominant value. You could think of it too, like two
pairs of complementary colors. Used wisely, it can be very meaningful.

This is a slight simplification of the color theory, but it's enough to kick
start you into the more complex tetradic palettes if you're interested.

    :root {
      --base-color: lime;

      --color-1: var(--base-color);
      --color-2: oklch(from var(--base-color) l c calc(h + 90));
      --color-3: oklch(from var(--base-color) l c calc(h + 180));
      --color-4: oklch(from var(--base-color) l c calc(h + 270));
    }

<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/WNLPQJw?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen WNLPQJw by web-dot-dev on Codepen">See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/embed/WNLPQJw"&amp;amp;gt;Pen WNLPQJw by web-dot-dev on Codepen&amp;amp;lt;/a&amp;amp;gt;</iframe>

#### Monochromatic with a slight hue rotation

Many color experts keep this trick up their sleeve. The problem is, a
monochromatic color scale can be quite boring. The solution is to add either a
minor or major hue rotation to each new color as the lightness is changed.

The following example decreases lightness by 10% each swatch and also rotates
the hue by 10 degrees. The result, a hotpink to indigo palette that seems to
seamlessly blend like a gradient might.

    :root {
      --base-color: deeppink;

      --color-1: var(--base-color);
      --color-2: oklch(from var(--base-color) calc(l - .10) c calc(h - 10));
      --color-3: oklch(from var(--base-color) calc(l - .20) c calc(h - 20));
      --color-4: oklch(from var(--base-color) calc(l - .30) c calc(h - 30));
      --color-5: oklch(from var(--base-color) calc(l - .40) c calc(h - 40));
    }

<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/QWoEVJO?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen QWoEVJO by web-dot-dev on Codepen">See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/embed/QWoEVJO"&amp;amp;gt;Pen QWoEVJO by web-dot-dev on Codepen&amp;amp;lt;/a&amp;amp;gt;</iframe>

##### Try out this leaderboard built with OKLCH and hue rotation

The following leaderboard interface uses this hue rotation strategy. Each list
item tracks its index in the document as a variable called `--i`. This index is
then used to adjust chroma, lightness and hue. The adjustment is only by 5% or
5deg, much more subtle than the above example with deeppink, and so it takes a
keen eye to notice the reason this leaderboard can be in any hue with such
elegance.

Be sure to change the hue in the slider underneath the leaderboard, and see
relative color syntax create beautiful color moments.

    li {
      --_bg: oklch(
        /* decrease lightness as list grows */
        calc(75% - (var(--i) * 5%))

        /* decrease chroma as list grows */
        calc(.2 - (var(--i) * .01))

        /* lightly rotate the hue as the list grows */
        calc(var(--hue) - (var(--i) + 5))
      );
    }

<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/YzdByvg?height=600&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen YzdByvg by web-dot-dev on Codepen">See the Pen &amp;amp;lt;a href="https://codepen.io/web-dot-dev/embed/YzdByvg"&amp;amp;gt;Pen YzdByvg by web-dot-dev on Codepen&amp;amp;lt;/a&amp;amp;gt;</iframe>