Color scheme without the help of a designer


Surely many of you have been in a situation where you need to quickly choose the colors for decoration, and the designer is busy with more important tasks, in a bad mood or on vacation. The task is not difficult, but sometimes you have to wait for an answer for several days.

My name is Emil Frolov, I am a techlide in the internal services team at DomKlik. In this article I will tell you how the idea of ​​a library was born, which now saves a lot of time when choosing colors. The solution is simple, but very useful, take it into service.

Especially if you need to choose colors for the dark scheme.

Task


DomKlik has a corporate portal, each section of which has its own color scheme. Previously, to create a new section, each time I had to torment designers and ask them to pick up a new set of colors and transfer them to the interface. It turned out a huge amount of extra code, spent a lot of time on correspondence, waiting and coordination. I really wanted to simplify and speed up the whole process.

We looked for ready-made solutions on the Internet, but nothing suitable was found. And then we decided to write a library: you enter the color (brand) that the designer gives, and the library selects a few more suitable colors. We also wanted the library to generate dark color schemes as well.
This is not a recipe for happiness, but rather an idea that everyone can develop within the framework of their project. You can see a small demo here .

Idea and solution


We thought how to do it. We started with a basic-based color-generation algorithm. On these Internet of yours again nothing ready was found. But they found a library that can change different color settings.

Many people know that there are many different color matching algorithms:



I don’t see any sense in painting them; they have already done this hundreds of times before me. But there are a few key points:

  1. For us they are redundant.
  2. I wanted to choose colors for myself.

Therefore, the designer and developer, merged into a single impulse, decided to pick up the scheme manually once.

To begin with, we described the basic colors:

export const getColors = (projectColor, inverse) => {
  ...
  const BASE_SUCCESS = '#00985f';
  const BASE_WARNING = '#ff9900';
  const BASE_PROGRESS = '#fe5c05';
  const BASE_ALERT = '#ff3333';
  const BASE_SYSTEM = '#778a9b';
  const BASE_NORMAL = '#dde3e5';
  const BASE_WHITE = '#ffffff';
  const BASE_BLACK = '#000';
  const TYPO_BASE_BLACK = '#242629';
  const TYPO_LINK = '#33BDFF';
  ...
}

Based on this, you can start creating. We get objects for working with basic colors.

  import color from 'color-js';

  export const getColors = (projectColor, inverse) => {
    ...
    const baseWhite = color(BASE_WHITE);
    const baseBlack = color(BASE_BLACK);
    const baseTypoBlack = color(TYPO_BASE_BLACK);
    ...
  }

I think it makes no sense to fully describe the entire selection of colors, but for an example I will give a couple of lines:

export const getColors = (projectColor, inverse) => {
  ...
  const bgBrand = baseProject;
  const bgHard = baseColor.setLightness(0.4);
  const bgSharp = baseColor.setLightness(0.18);
  const bgStripe = baseColor.setLightness(0.1);
  const bgGhost = baseColor.setLightness(0.07);
  ...
}

After we finished choosing the primary colors, we faced the following problem.

And what color display the text on the elements?

The task was not as difficult as it seemed. To solve it from the hex value, we need to get the brightness of the element. We did this with two helper functions. The first translates hex to RGB:

const hexToRgb = (hex) => {
  const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
  const newHex = hex.replace(shorthandRegex, (
    magenta,
    red,
    green,
    blue
  ) => red + red + green + green + blue + blue);

  const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(newHex);

  return result ? {
    red: parseInt(result[1], 16),
    green: parseInt(result[2], 16),
    blue: parseInt(result[3], 16)
  } : null;
};

The second function from RGB gets the brightness of the background and decides whether the color is dark or light:

export const getBgTextColor = (bgColor) => {
  const rgb = hexToRgb(bgColor);
  const light = (rgb.red * 0.8 + rgb.green + rgb.blue * 0.2) / 510 * 100;

  return light > 70 ? '#000000' : '#ffffff';
};

You might think that now everything is ready for the next step. But no, we still want support for a dark theme out of the box? Yes! We want it!

You probably noticed that we pass a flag to our function inverse. Let's change our code a bit with this flag in mind:

import color from 'color-js';

export const getColors = (projectColor, inverse) => {
  ...
  const BASE_SUCCESS = '#00985f';
  const BASE_WARNING = '#ff9900';
  const BASE_PROGRESS = '#fe5c05';
  const BASE_ALERT = '#ff3333';
  const BASE_SYSTEM = '#778a9b';
  const BASE_NORMAL = '#dde3e5';
  const BASE_WHITE = '#ffffff';
  const BASE_BLACK = '#000';
  const TYPO_BASE_BLACK = '#242629';
  const TYPO_LINK = '#33BDFF';

  ...

  const baseWhite = color(BASE_WHITE);
  const baseBlack = color(BASE_BLACK);
  const baseTypoBlack = color(TYPO_BASE_BLACK);
  const baseColor = inverse ? baseWhite : baseBlack;
  const typoColor = inverse ? baseWhite : baseTypoBlack;

  ...

  const bgHard = inverse ? baseColor.setLightness(0.4) : baseColor.lightenByAmount(0.85);
  const bgSharp = inverse ? baseColor.setLightness(0.18) : baseColor.lightenByAmount(0.95);
  const bgStripe = inverse ? baseColor.setLightness(0.1) : baseColor.lightenByAmount(0.96);
  const bgGhost = inverse ? baseColor.setLightness(0.07) : baseColor.lightenByAmount(0.99);

  ...
}

That's all. We can give a list of colors:

return {
  ...
    // BG
    'color-bg-hard': bgHard.toString(),
    'color-bg-sharp': bgSharp.toString(),
    'color-bg-stripe': bgStripe.toString(),
    'color-bg-ghost': bgGhost.toString(),
    'color-bg-primary': bgDefault.toString(),
  ...
}

In two days, with the help of our designer, I created a library, which at the output gives a color palette for a dark and light theme.

Next question: how to use it?

Very simple. To implement the generated color scheme, we used the insertion of CSS variables through the style block. This avoids conflicts with CSS variables that other CSS libraries use.

  const colors = getColors(color, themeKey === 'dark');
  const colorsVars = Object.keys(colors).map((key) => `--${key}: ${customColors[key]}`).join(';');
  
  const link = document.createElement('style');
  const headTag = document.getElementsByTagName('head')[0];

  link.type = 'text/css';
  link.id = 'project-theme-scope';
  const stylesBody = `:root {${colorsVars}}`;

  link.innerText = stylesBody;

  headTag.append(link);

And now the most delicious. Attention attention! Now, with a few lines, when we add dark theme support for half the elements:

  body {
    background: var(--color-bg-ghost);
    color: var(--color-typo-primary);
  }

The result is a library in which we pass the main brand color and get a set of CSS variables, which we then use to colorize our project.

Most of the time was spent on choosing a color scheme. I had to manually sort through a bunch of different parameters so that the colors were combined with each other. But now, at each iteration of the selection of colors for the new sections of the portal, we save several days.

Since the designer took part in the creation of the algorithm, there was still no case that he was dissatisfied with the colors generated by the library. Yes, and color schemes are not too large, it is difficult to make mistakes.

I emphasize once again that we do not claim to be the only right decision. This idea is implemented, and it works well. I tried to bring you the main points, and the details and scope of implementation depend only on you and your project.

Thank you for the attention.

All Articles