DEV Community

Cover image for A simple Color-Picker using CSS5 color-contrast() and color-mix()
Mads Stoumann
Mads Stoumann

Posted on • Updated on

A simple Color-Picker using CSS5 color-contrast() and color-mix()

Safari is often blamed for being “the new IE of web-browsers”. That's not fair, because Safari has recently been a “first-mover” in a lot of areas. For instance, Safari Technology Preview has implemented a lot of the stuff from the CSS Color Module Level 5 specification – color-contrast() and color-mix() among them.

Let's build a simple Color Picker using these cool new features! We'll add some JavaScript, using CSS.supports, to make it work in other browsers as well.

The markup is a simple fieldset with radio-buttons:

<fieldset class="color-wrapper">
 <!-- start iterate colors -->
  <label class="color" style="--bgc:hsl(168, 41%, 65%)">
    <input type="radio" name="cp" value="hsl(168, 41%, 65%)"><i></i>
  </label>
<!-- end iterate colors -->
</fieldset>
Enter fullscreen mode Exit fullscreen mode

We'll set a custom property, --bgc, for each color, and with a dash of CSS, this it how it renders in Chrome:

Chrome Basic


Using a mask() for selected color

We'll add a mask with a checkmark-icon to the <i></i>-element:

.color i {
  aspect-ratio: 1;
  background-color: transparent;
  display: block;
  mask: no-repeat center center/var(--ico-w) var(--ico);
  -webkit-mask: no-repeat center center/var(--ico-w) var(--ico);
  width: 100%;
}
Enter fullscreen mode Exit fullscreen mode

In Chrome, it now looks like this:
Chrome checked

Better – but the icon will have the same color, even if the background-color is dark, like the example above. It's also a bit annoying, that the border-color is the same for all colors.


color-mix()

With the color-mix-function, we can add a colored border, based on the custom property, --bgc, mixing in 10% of black:

.color {
  border: var(--bdw, 0.125rem) solid color-mix(in hsl, #000 10%, var(--bgc));
}
Enter fullscreen mode Exit fullscreen mode

color-contrast()

With the color-contrast-function, we can change the color of the icon, based on the custom property, --bgc:

.color input:checked + i {
  background-color: color-contrast(var(--bgc) vs white, black);
}
Enter fullscreen mode Exit fullscreen mode

In Safari, it now looks like this:

Safari

Cool! See those beautiful, dynamic border-colors! The checkmark-icon is white on dark colors, and black on light colors.

And absolutely no JavaScript is required!


Fixing issues in Chrome & Co.

In non-Safari browsers, we'll have to use some JavaScript in order to achieve the same:

if (CSS.supports('not (color: color-contrast(red vs black, white))')) {
/* code here */
}
Enter fullscreen mode Exit fullscreen mode

We'll add a method that'll iterate the labels of the fieldset, and set a custom property, --ico-c, to either black or white, depending on the brightness of the iterated color:

function setLuminance(elements) {
  elements.forEach(element => {
    const rgb = window.getComputedStyle(element).getPropertyValue('background-color'); 
    if (rgb) {
      const [r,g,b] = rgb.replace(/[^\d,]/g, '').split(',');
      const brightness = (299 * r + 587 * g + 114 * b) / 1000;
      element.style.setProperty('--ico-c', brightness <= 127 ? '#FFF' : '#111')
    }
  })
}
Enter fullscreen mode Exit fullscreen mode

The snippet will return the color of the label as rgb, no matter if it's hex, hsl or rgb to start with, then check it's brightness, and set the --ico-c-property.

In Chrome, it now looks like this:
Chrome final

Much better! The border-colors are still a bit dull in “non-Safari”-browsers ... In the setLuminance-method, we can fix that, by deducting 20 from each chanel (r, g and b):

element.style.setProperty('--bdc', `rgb(${r-20},${g-20},${b-20})`);
Enter fullscreen mode Exit fullscreen mode

chromecolors


Enabling color-contrast and color-mix

In Safari Technology Preview, go to Develop > Experimental Features and enable them:

Safari Tech Preview


Demo


Closing thoughts

I tried to use color-mix within color-contrast – but that didn't work!

I guess the implementation is not completely done, and thus a little buggy – but I'm looking forward to being able to mix in 80% white or black with the background-color, so the icon-color blends in more “softly” with the background-color.

Discussion (0)