Colour Conundrum

CSS
back
March, 2025
8 min read

Do you ever wonder why there are so many ways to declare the colours?

Some (long) while ago, I was reading posts about new colours, you know, those HSL, OKLCH, HWB, LAB, and LCH. Also, there is the Color() function as well as Relative colours.

That made me think - why? Why do we need all those different ways to declare the colour? For the last 15 years, I have happily used RGB, RGBA, hex, or even by names like purple, orange or #bada55. I have never had any issues. Why all the fuss?

Until one day when I finally realised that.

What is OKLCH?

Just a quick recap before we dive in.

OKCLH is designed to map colours in a way that aligns with how humans perceive them. Also, the values are much easier to play with than remembering hex or RGB().

L - Lightness (0% - 100%)
C - Chroma (Saturation) (0 - 0.37)
H - Hue (0 - 360)
a - optional opacity (0 - 1 or 0 - 100%)

It is worth mentioning that if you set two colours by using OKLCH with good accessibility value, then by changing Hue values, you will keep the same good accessibility ratio. That one benefit is already a fantastic win!

You can play with those values down the post and see how that accessibility works.

Component with colourful link

Anyway, back to the problem. I had a simple component or widget, if you like, with a Title, Description and Link. Something like this:

Component title

Lovely description for the component

The colour and background of the link could be improved.

There is not much there, but those simple snippets are many and assigned to categories, which have defined colour schemas, exactly like those backgrounds on the buttons below.

CSS Fonts JavaScript Development Tools
1
--pastel-orange: #FFD79F;
2
--pastel-cream: #F0EBD5;
3
--pastel-mint: #D1E2CF;
4
--pastel-yellow: #FCF3A4;
5
--pastel-rose: #FFA3B6;

So, my next thought is: how about I apply the same colour schema for the links? That way, I would make it easy to distinguish between different snippets.

This is where I realised that I’m in trouble. Suddenly, I need three colours:

  • Link background colour
  • Link background hover colour
  • Link text colour, should be strong against the background but keep the same hue.

Oh, did I mention that I also need something for a light/dark theme? That means I need six colours from one pre-defined colour.

Here is an example of what I would need to create with one colour:

Component | light theme

This component uses Tools category colours for link

Light version link for Tools category

Component | dark theme

This component uses Tools category colours for link

Dark version link for Tools category

So, the Tools category uses --pastel-rose: #FFA3B6; colour, and I would need to create six colours from that colour. There are three colours for each theme and 10 categories. That's like 60 colours to define in variables and reuse. It feels wrong.

1
a {
2
  color: #7a2c41;
3
  background: #ffc9d3;
4

5
  &:hover {
6
    background: #ffeef0;
7
  }
8
}
9

10
/** Dark theme */
11
a {
12
  color: #ffe0f3;
13
  background: #573f44;
14

15
  &:hover {
16
    background: #6e4d53;
17
  }
18
}
19

Discovering the power of oklch()

While I was tinkering with the problem (well, actually randomly browsing blueSky) I stumbled upon Ahmad Shadeed's post on relative colours: CSS relative colors.
This, in turn, led me to the post from Evil Martians: OKLCH in CSS, and that's when I realised I wanted to rewrite all the colours in my blog to use OKLCH (someday).

Once I realised the power of OKLCH and relative colours, the answer is simple.

I can take one colour and create multiple colours in turn. Actually, I can take any colour and reuse that colour to create corresponding colours in CSS. Simple.

In my case, each snippet loops through the config file and gets assigned a colour. Instead of assigning colour directly like color: var(--pastel-orange), I assign a new variable to hold the current snippets colour value, like this <a style="--link-color: var(--pastel-orange)">link here</a>

This is important. Instead of assigning a colour to the link, I assigned a variable with the current colour. Because of that, I can get this value and use it in the CSS later! Very nifty!

Now, the magical part in CSS:

1
<!-- HTML part -->
2
<div>
3
  <h3>Title</h3>
4
  <p>Description</p>
5
  <a style="--link-color: var(--pastel-orange)" href="#">link here</a>
6
</div>
1
/** CSS part */
2
.link {
3
  background: oklch(from var(--link-color) l c h / 0.6);
4
  color: oklch(from var(--link-color) calc(l - 0.4) c h);
5

6
  &:hover, &:focus {
7
    background: oklch(from var(--link-color) l c h / 0.2);
8
  }
9
}
10

11
/** For the dark theme, all I need to do is adjust lightness and opacity */
12
[data-theme="dark"] .link {
13
  background: oklch(from var(--link-color) l c h / 0.3);
14
  color: oklch(from var(--link-color) calc(l + 0.2) c h);
15
}

If you freak out when looking at this code, that's normal. But let's go step by step. Honestly, it is all very simple.

Let's look at line 3: background: oklch(from var(--link-color) l c h / 0.6);

I assign background colour and use relative colour oklch

I created that colour from var(--link-color) which I assigned to the element. Remember this line:
<a style="--link-color: var(--pastel-orange)">link here</a>
so, I assign var(--pastel-orange to --link-color and use it in oklch.

The oklch has three values Lightness, Chroma, Hue, and the last value (0.6) is opacity. Basically, I take --link-color and pass it in relative color in the same way but with opacity 0.6. That's it.

For line 4
color: oklch(from var(--link-color) calc(l - 0.4) c h);,
I change the lightness.

The value range for the lightness is from 0 to 0.37. When I create OKLCH, it simply assigns all values to the respective channel. Then I use calc(l - 0.4) to reduce lightness to my liking.

The beauty of this approach is that I do not need to know the exact colour; I can simply change one of the parameters, and it will adjust colour by lightness, hue, or opacity. You can't simply do that with RGB() or hex colours, well, apart from opacity.

I use exactly this approach in Bookmarks. Try to open the dev tools and inspect yourself.


{
  background: oklch(90% 0.084 75);
}
	 

Practicalities

Ok, you may be still not convinced, but let me give another example.

Imagine you have text and a background. You want the text to always be easy to read against the background and keep both colours similar to each other. How would you do it with RGB() or hex colours? You would need to declare every background and every prime colour for the text, right? I'm too lazy to do that, so here is how I would do it with oklch().

In the example below, I assign only one colour to the —-figure-main and reuse it in both examples.

Always prominent text against a background

Here it is the same but with reverse colours.

Always prominent text against a background

1
<div style="--figure-main: oklch(0.9 0.084 75)">
2
  <figure style="background: oklch(from var(--figure-main) l c h)">
3
    <p style="color: oklch(from var(--figure-main) calc(l - 0.4) c h)">Text</p>
4
  </figure>
5

6
  <figure style="background: oklch(from var(--figure-main) calc(l - 0.4) c h)">
7
    <p style="color: oklch(from var(--figure-main) l c h)">Text</p>
8
  </figure>
9
</div>
10
	

Notice that the only thing I change is one parameter - hue.
The rest is calculated in CSS automatically while keeping the colour ratio.

Here you have it! Finally, it clicked for me, and I can see fantastic opportunities to use OKLCH for design systems and reuse one colour for dark/light themes with reversed lightning.
Also, since OKLCH works with how humans perceive colours, you can build better colour contrast designs and improve accessibility with ease.

I bet I only scratched the surface of all the possibilities.

Let me know your thoughts on BlueSky or Mastodon.

P.S. This post was written by a human, and no AI was harmed in the process.

Andris Švarcs

Somehow, I've survived over 15 years as a web developer without losing my interest in the craft. Quite the opposite, with so many great improvements in the Web standards, what was nearly impossible now is easy to make.

My career has been a wild ride through small agencies and big corporations, building everything from finance apps to health dashboards.

I'm that annoying person who needs to understand products beyond just slinging code. I ask questions like 'Why is this feature important?' and 'How will this improve the customer journey?' – you know, the kind of questions that make project managers reach for the pint aspirin. This curiosity has led me down the rabbit holes of design, accessibility, and SEO. Because apparently, making websites pretty, usable, and findable wasn't challenging enough on its own.

P.S. If this bio sounds too polished, blame my evil AI twin. I'm still working on teaching it sarcasm.

Copyright © since 2021, Andris Švarcs. All rights reserved.

Lets connect

bluesky

youtube

linkedin