Jump to content

LESS, color calculation color models


Recommended Posts

As I've been working through an admin theme structure that would allow me to add 1-3 colors and relationship types that would generate accent and different primary, secondary and muted colors for UIkit.

There have been a number of interesting bumps along the way connected to color and contrast and this has led me through several rabbit holes that might save folks some time as they look to build their own admin theme and find themselves unsure about how to wrangler the color contrast issue.

So I started with a basic color input and calculated a number of additional colors. I started with red.


in LESS, I set up a few rules to calculate and arrange different colors:

@base-dark-15-desat: darken(@base-color,15);
@base-dark-30-desat: desaturate(darken(@base-color,30),20);
@base-dark-35-desat: desaturate(darken(@base-color,35),50);
@base-light-15-desat: desaturate(lighten(@base-color,15),50);
@base-light-15: lighten(@base-color,25);
@base-dark-15: darken(@base-color,25);
@base-dark-35: darken(@base-color,35);

Originally desaturated the first one, but changed my mind.

All of these colors seemed to work fine for the base of #ff0000 although I needed to provide a little tweaking to get the link color usage right, because I found that when I chose other color, the contrast of the link text against the background didn't always work. It was fine for buttons and other navigation, but not link text on the main background.

Blue and green, for example:


The blue of the button, if applied to the link text above, would have been too dark of a contrast. In stead I brought the lightness up and it gets a sort of periwinkle tone.

Green was dramatically dark:


Now this piece took me down my first rabbit hole - what exactly was desaturation, lighten and darken doing to achieve their result. My assumption was that they were mixing with white or black - which LESS can certainly do.

That's not what was happening here. LESS convert colors to HSL before running any color functions. And the percentage argument that comes with the lighten and darken commands does not actually brighten or darken the color by the percent you specify. Instead, it sets the absolute luminance value of the HSL value of the color to the value you state. Throw desaturation on top of that process

The problem is that not all luminance values are equal. You can have two very different colors that appear to have different brightness levels, but their luminance levels are identical. If a luminance level is dark enough or light enough, using lighten will flatten the color to white, and darken will flatten the color to black.

The percentage that merely dimmed the red color to a deeper brick red in combination with desaturate turned the base green color black.

What can be done? It is possible to get the luminance value for a color through:

lightness( color );

Then that value can be multiplied by a float between 0.01 and 1 to get an adjustment that can be added to the adjustment.

This provides a percentage change from whatever the lightness level is that a color intrinsically has. But this feels a bit janky.

Moving on to the next topic, and one that really captured by attention for a few days - complementary color calculation.

The formulas here were similar to the single color variant - with the addition of a LESS complement calculation:

@base-contrast: difference(@base-color, #ffffff);

@base-dark-15-desat: darken(@base-color,15);
@base-dark-30-desat: desaturate(darken(@base-contrast,30),20);
@base-dark-35-desat: desaturate(darken(@base-contrast,35),50);
@base-light-15-desat: desaturate(lighten(@base-color,15),50);
@base-light-15: lighten(@base-color,25);
@base-dark-15: darken(@base-contrast,25);
@base-dark-35: darken(@base-contrast,35);

This was strange to me. I was expecting violet as a complementary color.

Not so, there is a rabbit hole (and a controversial one at that) connected to the legacy use of subtractive color spaces and additive color spaces.

This article (and the connected libraries on github) explained it quite well:


My expectation was connected to the RYB color wheel:

RYB Color Wheel

not the RGB color wheel:

RGB Color Wheel

So now, the thing about this situation is that even the CYMK color space - which developers and designers understand to be dull and muted when presented on monitors - is different still than RYB space, as it uses a more extensible primary color scheme that can cover gamut ranges that RYB can't touch.

And yet, for some reason, RGB strikes me as having a lot of garish colors while RYB colors produce a warmer space.

He's got a fun color picker for nostalgic folks like me:


Again, like the luminance cludge, trying to bounce around color space to get the effect I want led me to wonder - how are contemporary apps doing it? What color space are they using to work out all the accessibility and contrast issues?

This led me to look into HCT - hue, chroma, tone - color space that is used by Material Design 3:



Really cool conceptually, the science behind the new color space is described in detail here:


Regarding the issue we run into with LESS using lighten/darken - they give a great example of four colors that have the luminance but perceptually vary considerably in brightness:


I haven't finished running calculations on how to best address color selections for contrast scores but these items gave me a lot to think about.

There are Material 3 dev libraries available - I'll probably check them out this week.


My guess is that this sort of color comparison goes into Chrome's contrast scoring?

Being able to use a library to automatically calculate appropriate contrast color sets will probably end up being a better alternative to using LESS functions in the future.

  • Like 4
  • Thanks 1
Link to comment
Share on other sites

  • 4 weeks later...

I recently came across another interesting palette visualization option used in astronomy visualization and enhancements.

cubehelix - https://www.mrao.cam.ac.uk/~dag/CUBEHELIX/

It gives a broader spectrum of colors while maintaining linear perceived brightness levels.

my `default' cubehelix colour scheme

Was messing around with its implementation in chroma.js


Link to comment
Share on other sites


  • Recently Browsing   0 members

    • No registered users viewing this page.
  • Create New...