So you want to create a design system, pt. 2: Colors
8/4/2021
So you want to create a design system, pt. 2: Colors

Color is probably the most distinctive element of any design, and also the most important expression of brand identity (at least until Material You completely reverses this relationship, but it remains to be seen how it will be adopted). So how do we approach color when designing and implementing a design system so that our solution is usable, versatile, and scalable?

Color me curious

Besides conveying the brand and evoking emotions, colors have several other roles in current applications, including:

  • highlighting different application states such as errors, warnings, success, or info messages
  • ensuring usability, legibility, and accessibility of the application under all conditions
  • providing different themes from which the user (or system) can choose according to environmental conditions or personal preferences

Regarding the last point, users nowadays expect support for at least light and dark themes. Often this is more than just an aesthetic choice - for example, a car navigation app that dazzles drivers at night with large areas of bright colors can be downright dangerous.

And while the app supports switching between themes, it doesn't have to stop at just these two basic ones, for example:

  • Is accessibility extremely important to your app? Add a specially designed high-contrast or colorblind-friendly theme.
  • Does the app owner currently run a major promotion, have an anniversary, or celebrate some significant event? Make it known with a special temporary theme.
  • Do you want to differentiate products or make it clear that the customer bought a premium version of the app or service? Add a special, more luxurious-looking theme.
Various application themes

Theme support is a feature that is unique in that it makes both users and the marketing department happy. But how to construct it so that both designers and developers can actually work with it and be productive?

Layers of indirection

Let's start with what is definitely not suitable: Hardcoding the colors in the design tool and therefore in the code.

Do not hardcode colors

There are obvious drawbacks to this method, including the inability to change colors globally in a controlled manner (no, “find & replace” isn’t really a good idea in this case), and the need to copy and edit all designs for each additional theme we want to add (for designers), or cluttering the code with repetitive conditions (for developers). It also often leads to inconsistencies and it’s extremely error-prone - did you notice the mistake in the picture above?

Unfortunately, we still occasionally encounter this approach because many design tools will happily automagically present all the colors used, even if they are hardcoded, creating the illusion that the colors are under control and well-specified. They aren’t. Don’t do this.

So how to get rid of hardcoded colors? The first step is to hide them behind named constants and reuse these constants in all places.

Do not use color constants alone

This is definitely better - the colors can be changed globally in one place, but the problem arises when supporting multiple themes. The naive solution is to override each constant with a different value in every theme. This works as long as the colors in the different themes change 1:1. But consider the following situation:

Do not override color constants per theme

Since it is usually not advisable to use large areas of prominent colors in a dark theme, although the toolbar and button in a light theme are the same color, the toolbar should be more subdued in a dark theme. This breaks the idea of overriding the colors 1:1 in different themes because where one theme uses a single color, another theme needs more colors.

The solution to this situation is the so-called (and only slightly ironic) fundamental theorem of software engineering:

“We can solve any problem by introducing an extra level of indirection.”

In this case, that means another layer of named color constants. I kid you not - please stay with me, it’ll be worth it.

The solution

We achieve our goals, i.e. the ability to easily and safely change colors globally, and support any number of themes, by following these steps:

  1. Define a set of semantic colors. These are colors named and applied based on their purpose in the design. Their names must not express specific colors or shades, but roles. For example, Google’s Material Design defines the following semantic colors: Semantic colors of Material Design These names are a good starting point, but of course, you can define your own, based on your needs. What's important is that semantic colors don't have concrete values by themselves, they are placeholders or proxies that only resolve to specific colors when applied within a specific theme, meaning one semantic color will probably have a different actual value in each theme.
  2. Define a set of literal colors. These constants literally represent the individual colors of your chosen color palette. They are common to all themes, so there are usually more of them than semantic colors. Unlike semantic colors, they are named purely on the basis of their appearance. For example, an earlier version of Material Design defined the following shades: Old Material Design literal colors Recently it has become a common practice to distinguish colors with different lightness using a number where 1000 is 0% lightness (i.e. black) and 0 is 100% lightness (white), but of course you can devise your own system.
  3. Follow this rule in both design and code (no exceptions!):
    Semantic colors must be used exclusively and everywhere. Literal colors (or even hard-coded constants) must never be used directly.
    This means that the use of colors in design and implementation must have the possibility of being completely specified in the form of "wireframes" like this: Design wireframe specified with semantic colors
  4. Finally, map semantic colors to concrete literals per theme. This step ultimately produces a specific theme from the design specification, which is in itself independent of a particular theme. Based on our previous example, the final result will look like this: Themes resolved from semantic colors mapped to color literals For example, toolbar background color is specified as Primary, which in Light theme is mapped to Purple700 literal color, but in Dark theme it resolves to Purple900. The most important thing is that Purple900 or Purple700 literal colors aren't mentioned in the design specification, only in theme definition.

It's just a little extra work, but the benefits are enormous. We have successfully decoupled the definition of the colors from the actual colors used in various themes.

Make it work for you

There are usually questions that often arise or choices that need to be made when implementing this system. Here are some tips based on our experience:

  • Don't go overboard with the number of semantic colors. It's tempting to define a separate semantic color for every part of each UI element (e.g., ButtonBackground, SwitchTrack, ProgressIndicatorCircle), which has the theoretical advantage that you can then change them independently, but it also makes it much harder to navigate the design and implementation. The ideal amount of semantic colors is one where one can hold more or less all of them in one's head at once. Try to find a minimum set of sufficiently high-level names that will cover 90% of situations. You can always add more names later.
  • Naming is hard. Since semantic colors form the basis of the vocabulary used across the team and also appear everywhere in the code, it's a good idea to spend some time looking for the most appropriate names. If some of the chosen names turn out to be not that fitting, don't be afraid to refactor them. It's unpleasant, but living with inappropriate names for a long time is worse.
  • Never mix literal and semantic names. For example, a set of semantic colors containing Orange, OrangeVariant, Secondary, Background, Disabled, etc. isn’t going to work well, even if the main color of your brand is orange and everyone knows it. Even so, create a purely semantic name for such a color, like Brand or Primary.
  • If you need multiple versions of a semantic color, never distinguish them with adjectives expressing properties of literal colors such as BrandLight, BrandDark, etc., because what is darker in one theme may be lighter in another and vice versa. Instead, use adjectives expressing purpose or hierarchy, such as BrandPrimary, BrandAccent, or even BrandVariant (but if you have Variant1 thru Variant8, you have, of course, a problem as well).
  • For each semantic color that can serve as a background color, define the corresponding semantic color for the content that can appear on that background. It's a good idea for these colors to contain the preposition on or the word content in the name, like OnPrimary or SurfaceContent. Avoid the word text (e.g., SurfaceText), as this color will often be applied to other elements such as icons or illustrations, and try to avoid the word foreground because sometimes the use of background and foreground colors can be visually inverted: Two components with inverted colors
  • The use of the alpha channel in literal colors is a difficult topic. Generally speaking, the colors that will be used as backgrounds should be 100% opaque to avoid unexpected combinations when several of them are layered on top of each other (unless this effect is intentional). Content colors, on the other hand, can theoretically contain an alpha channel (useful, for example, for defining global secondary or disabled content colors that work on different backgrounds), but in this case, it is necessary to verify that the given color with its alpha value works with any background.
    Another question is alpha channel support in your design tool and code - is the alpha value an integral part of the color, or can we combine separate predefined colors and separate predefined alpha values?
  • If your design tools don't directly support semantic colors or multiple themes at the same time, work around that. Tools come and go (or, in rare cases, are upgraded), but your design system and the code that implements it represents much more value and must last longer. Don’t be a slave to a particular tool.
  • All text should be legible and meet accessibility standards (icons on the other hand don’t need to do that, but it’s generally a good idea for them to be compliant as well) - see The Web Content Accessibility Guidelines (WCAG 2.0), and use automated tools that check for accessibility violations.

Design is never finished

...so it's important that your design system is able to evolve sustainably. This way of defining color, although certainly not the simplest, allows for exactly that. We'll look at other fundamental elements of design systems and how to handle them next time.

Tags

#design-system; #ui; #ux; #development; #android; #iOS

Author

Jiří Hutárek

Versions

(none)