baxter.sh

Naming OKLCH colours

I really like using variable names for colours1 rather than just the plain CSS hex code. It feels nicer to talk about colours by their names.

Sometimes I like to pick colour names based on the context in which they’re being used, but other times I just want a nice name, and I like using Chirag Mehta’s “Name that Color” tool.

Up until recently the colours I used on this blog for highlights were:

:root {
  --flamingo:       #f14a38;
  --mojo:           #c44536;
  --burnt-sienna:   #E9724C;
  --pale-prim:      #FDFEB8;
  --mango:          #FFC857;
  --wasabi:         #9EA93F;
  --verdigris:      #38A3A5;
  --curious-blue:   #19a0de;
  --byzantine:      #5569ee;
  --lilac:          #A06CD5;
}

Which, when placed side by side, looked like this:

flamingo
mojo
burnt sienna
pale prim
mango
wasabi
verdigris
curious blue
byzantine
lilac

And I quite liked them, but recently I felt a bit dissatisfied with the way that the colours weren’t evenly spaced out, in terms of their hue, and weren’t consistent in terms of their intensity or brightness.

After reading about OKLCH I wondered if picking a single colour and rotating through the hue would give me a better set of colours.

I used oklch.com to try out a few different colours. It has a really nice feature that lets you see whether the combination of chroma and lightness are likely to result in a visible colour as you adjust the hue.

Screenshot of graphs from oklch.com showing Chroma and Hue graphs from OKLCH.com.

After trying a few different options I settled on oklch(0.7 0.15 30), which I felt looked reasonable:

I then created 9 other colours, each 10° apart:

If I was using hex codes then I’d use “Name that Color” to get a nice name for each of them, but obviously that isn’t going to work with OKLCH.

Thankfully there’s a JavaScript library called Culori that can convert RGB to OKLCH, and there’s also a JavaScript library called ntcjs which produces the same colour names as the “Name that Color” site.

So running:

import { oklch, formatHex } from 'culori';
import ntc from 'ntcjs';

const oklchColors = [
  { l: 0.7, c: 0.15, h: 30},
  { l: 0.7, c: 0.15, h: 60},
  { l: 0.7, c: 0.15, h: 90},
  { l: 0.7, c: 0.15, h: 120},
  { l: 0.7, c: 0.15, h: 150},
  { l: 0.7, c: 0.15, h: 180},
  { l: 0.7, c: 0.15, h: 210},
  { l: 0.7, c: 0.15, h: 240},
  { l: 0.7, c: 0.15, h: 270},
  { l: 0.7, c: 0.15, h: 300}
];

oklchColors.forEach(color => {
  const oklchColor = {
    mode: 'oklch' as const,
    l: color.l,
    c: color.c,
    h: color.h
  };

  const hexColor = formatHex(oklchColor);
  const [_closestHex, colorName, _isExact] = ntc.name(hexColor);
  const safeVarColorName = colorName.toLowerCase().replace(/\s+/g, '-');
  console.log(`  --${safeVarColorName}: oklch(${color.l} ${color.c} ${color.h});`)
});

Gives:

:root {
  --burnt-sienna:     oklch(0.7 0.15 30);
  --zest:             oklch(0.7 0.15 60);
  --buddha-gold:      oklch(0.7 0.15 90);
  --sushi:            oklch(0.7 0.15 120);
  --chateau-green:    oklch(0.7 0.15 150);
  --caribbean-green:  oklch(0.7 0.15 180);
  --cerulean:         oklch(0.7 0.15 210);
  --picton-blue:      oklch(0.7 0.15 240);
  --malibu:           oklch(0.7 0.15 270);
  --biloba-flower:    oklch(0.7 0.15 300);
}

And for the sake of completion, here are the colours side by side with their names:

burnt sienna
zest
buddha gold
sushi
chateau green
caribbean green
cerulean
picton blue
malibu
biloba flower

Nice!

  1. I spell “colour” with a “u” where I can, because I am in the UK, but I appreciate that CSS uses “color”.