Automatically creating an accessible color palette from any color? Sure!

How do you let your users chose any color, but still make sure that the colors make your app useful and accessible? You create rules.

Find even more details and resources here: https://confrere.com/a11y

To pick or not to pick a brand color

The natural way to let users configure their Confrere, so it looks more like their own brand, is to let them pick their own brand color. To have buttons, links and so on in the brand color seems like a no-brainer.

Should we really let them make this choice?

But it’s easier said than done. One brand might have a dark blue, another one a pale pink. Would you be able to read the text on a dark blue button? And would a pale pink button stand out as a call to action?

Letting users choose any color opened for a lot of issues, but it also made it clear that we wouldn’t be able to guarantee that the colors used in the interface had a sufficient contrast to be accessible (ast least 4.5:1 to fulfill WCAG AA). Not only am I convinced that striving for accessibility is the right thing to do — in Norway most of the WCAG AA criteria are enforced by law for any site or app directed at the general market.

So what do you do? Well, put a graphic designer (Eivind Molvær), a developer (Dag-Inge Aas), a designer/frontender (Jørgen Blindheim) and a UX designer (me) in a room and let them agree on some steps and rules.

Step 1: Get to know CSS Custom Properties

Central to our solution are CSS Custom Properties, more widely known as CSS Variables. These are values we can set and modify and reference at any point in our CSS, following the same cascading principles that we all know and love from CSS. With these, we can define any CSS value and reference it, which makes CSS Custom Properties perfect for storing color values.

body {

--color-primary: tomato;

background-color: var(--color-primary);

}

We are also able to set these properties from JavaScript easily, making it possible to create powerful theming engines that can change whole color schemes on the fly.

document.body.style.setProperty("--color-primary", "tomato");

With these tools in hand, we set out to create our solution.

Step 2: Black or white button text?

First off, we figured we could do a simple contrast check to see if the brand color validated against the black button text. If the brand color doesn’t validate against black, we change the button text to white. With a little help from the npm package color, we can do this automatically:

// in Theme.js

import Color from "color"; const primaryColor = Color(brandColor);

const primaryColorContrast = primaryColor.light()

? Color("black")

: Color("white"); document.body.style.setProperty("--color-primary", primaryColor.string());

document.body.style.setProperty("--color-primary-contrast", primaryColorContrast.string()); // in Button.css

.button {

background-color: var(--color-primary);

color: var(--color-primary-contrast);

}

Yellow button? No need to change the color of the button text here :)

Dark blue button? White is a lot more legible than black!

Step 3: Generate lighter and darker color variants

We can’t use the same brand color everywhere. However, having the users specify their own secondary colors would add too much complexity. That’s how we came up with the idea to generate lighter and darker versions of the brand color.

To create the lighter version, we mix 20% of the brand color with 80% white. To create the darker version, we mix 80% of the brand color with 20% black, and then we check to see if it validates against white. We keep adding 10% black until the new darker color validates against white.

import Color from "color"; function getValidatedColor({

colorToChange,

colorToValidate = Color("white"),

minimumContrastRatio = 5,

mixingColor,

mixingAmount,

tries = 0,

maxTries = 8,

}) {

const newColor = colorToChange.mix(mixingColor, mixingAmount);

if (

newColor.contrast(colorToValidate) < minimumContrastRatio &&

tries < maxTries

) {

return getValidatedColor({

colorToChange: newColor,

mixingColor,

mixingAmount: 0.1,

tries: ++tries,

});

}

return newColor;

} const primaryColor = Color(brandColor);

const primaryColorLight = getValidatedColor({

colorToChange: primaryColor,

mixingColor: Color("white"),

colorToValidate: Color("black"),

mixingAmount: 0.5,

});

const primaryColorDark = getValidatedColor({

colorToChange: primaryColor,

mixingColor: Color("black"),

mixingAmount: 0.2,

}); // then set these as CSS Custom properties

This way, you end up with automatically generated palettes like these: