Create your design system, part 3: Colors

In this article, we’ll take a look at how to set a color system in CSS, and what are the best practices to make sure the system is easy to use and maintain.

This article is part of a series on design systems inspired by our library of web components. The library relies on a solid system of CSS globals. So this is us sharing the things we’ve learned setting the global style of our library! ✌

Article Series:
- Part 1: Typography
- Part 2: Grid & Layout
- Part 3: Colors
- Part 4: Spacing
- Part 5: Icons
- Part 6: Buttons

Color Variables 101

Unlike other CSS globals, creating a color system is 10% about coding and 90% about semantics. While working on your _colors.scss file, you want to keep in mind the following goals:

  1. The color variables have to be easy to remember → You don’t want to check the global file anytime you have to pick a color.
  2. The system has to be easy to update → You will add, remove, and rename colors. Make sure doing so is not complicated.
  3. The system should include only the essential colors → we’ve heard this one so many times…yet we always end up with more colors than we need! The real success key of a design system is removing all that is not necessary (colors included).

Semantic vs Declarative colors

When it comes to setting the color variables, there are two main approaches: semantic and declarative colors.

The semantic approach looks like:

While here’s an example of a declarative approach:

Neither of them is wrong. Picking the one that meets your needs depends on so many factors (e.g., project size, branding colors relevance, etc.).

While working on the _colors.scss file of our framework, I had to take into account users were going to edit it (100%). That means that even if the declarative approach was the easiest to use, I had to mix it with the semantic approach to get a system that was also easy to maintain.

The essential color palette

Step number one was declaring the minimum number of colors needed to create the web components. In general, the essential color palette is composed of:

  1. The main/primary color → used for the links, the button background color, etc. In general, it’s the main call-to-action color.
  2. The accent color → used to highlight something important on the page. It shouldn’t be a variation of the primary color, but a complementary color.
  3. A scale of neutral colors → It’s generally a scale of grayscale tones, to be used for text elements, subtle elements, borders, etc.
  4. Feedback colors → success, error, warning.

Some of these colors need a variation (darker/lighter version), often used to highlight interactivity (e.g., :hover/:active states).

In CSS, this translates to:

*note: we’re using the postcss-color-mod-function plugin to translate the color functions into RGBA code compatible with all browsers.

The snippet above represents the color palette: all the colors used across the project.

The variations of the primary and accent colors are generated using color functions. This approach comes in handy if you have a demo.html file (and we do in our framework) so that you can tweak the values of the functions until you’re satisfied with the colors obtained. The shades (or neutral) colors are generated using chroma.js. In this case, using the functions was not ideal, because you generally have two opposite colors (black and white), and you need to generate a scale of values based on these two colors.

Adding semantic colors to the mix

Once the color palette is ready, we can add semantic colors. Creating semantic colors does not mean incrementing the number of colors, but distributing the colors using semantic references.

Why I think this is a good approach

First of all, this system relies on two essential colors: primary and accent colors. That means that when you need to use the color variables, it won’t be difficult for you to remember what those variables represent (even if you are not using declarative names such as “blue” and “red”).

Your system may need to include more colors (e.g., a secondary color). You’re still dealing with just three colors. Managing a system based on 10+ main colors would be difficult regardless of the approach you’re using, so you may want to consider simplifying it.

The grayscale colors use a different naming convention: the higher is the number at the end of the variable, the darker is the color. 
This approach becomes handy when you’re not sure which neutral color you want to apply. If gray-2 looks too subtle, you can try gray-3. You may have noticed some shades are missing (e.g., gray-5). They were not essential in our case (we never used them while creating the web components), so we removed them from the color palette.

Semantic colors are added to the mix for three main reasons:

  1. The _colors.scss file becomes the source of truth anytime you need to modify a color. Do you feel the text heading elements should be darker? Open the _colors.scss file and edit the color-text-heading variable.
  2. If you define a color-border, for example, then you won’t need to look up which gray color you’ve been using in other components the next time you create a border element. The same concept applies to many elements, not just borders.
  3. It makes it a piece of cake to create and maintain different themes.


As soon as we can use CSS variables without having to rely on plugins or polyfill, creating color themes will be super simple*! Does that mean we can’t create themes today? No, we can. We have two options.

*in our framework, we use the postcss-css-variables plugin to compile CSS variables. It currently does not support updating variables in a CSS class.

Option 1 is updating CSS variables anyway. Browsers that don’t support variables will show the “default” color theme. This is not an issue, as long as the content is accessible.

For example, you have a default color theme → white background and black text color, and a .theme-dark → black background and white text color. Then you create two components, one with the default theme, the other with the .dark-theme. If having both components with the default theme does not affect the user experience, then you can consider the .dark-theme as an enhancement (optional). In this case, it makes sense to update the variables to create different themes even if they’re not supported everywhere.

This is how you create a new theme updating some key CSS variables:

I love this solution because it abstracts the color correction, and it allows you to keep your color themes in a single file. By doing so, we can potentially change the state of each component (from theme-a to theme-b) simply by applying a CSS class.

Option 2 would be targeting all elements whose appearance is affected by the theme. The advantage of this method is that it’s supported by all browsers. However, it’s not as easy to maintain compared to the one based entirely on CSS variables.

Here’s an example of option 2 in action:

Now you know how we’re planning to handle colors in our framework! If you have feedback/suggestions, let us know in the comment!

I hope you enjoyed the article! For more web design nuggets, follow us here on Medium or Twitter. 🙌