Monday, April 12, 2021

Managing CSS Colors Systems with a Single Source of Truth

Our team recent­ly worked through the first phase of a large gov­ern­ment plat­form run by a com­po­nent design sys­tem. The goals were to cre­ate a set of visu­al themes that could sup­port acces­si­bil­i­ty, native light- and dark-mode switch­ing, and a set of con­tent com­po­nents that were flex­i­ble enough to sup­port more than 70 gov­ern­ment agen­cies. There is quite a bit of com­plex­i­ty to the sys­tem, but what we’d like to focus on right now is how we are man­ag­ing the col­or system.

The sites are still evolv­ing, but the cur­rent count is five col­or themes, each with a light- and dark-mode, using a total of 46 col­ors. We decid­ed to use Pat­tern­Lab to man­age our design pat­terns, which means that each com­po­nent is com­prised of its own Sass, JS, and Twig files pack­aged togeth­er in a portable way. It also means that we could lever­age cus­tom Gulp process­es to make some pret­ty cool stuff happen.

First, our goals of using Pat­tern­Lab and cre­at­ing a sin­gle source of truth:

  • Define a col­or once and in one place and make its def­i­n­i­tion avail­able to Sass and Twig
  • Define col­or themes once and in one place and make those def­i­n­i­tions avail­able to Sass, Twig, and PHP
  • Define col­ors in HSL(), which is more human-read­able, mak­ing it easy to under­stand the rela­tion­ships between col­ors, while allow­ing con­ver­sion to what­ev­er col­or space we might need For the gov­ern­ment employ­ee using this sys­tem, our goals were to:

  • Allow authors the choice of back­ground col­ors from defined theme options with­out con­trol­ling fore­ground col­ors — this takes the abil­i­ty to cre­ate inac­ces­si­ble col­or com­bi­na­tions out of their hands

  • Allow authors to design con­tent for their own pre­ferred col­or scheme (light or dark) and be con­fi­dent that it will look great for those who use the oth­er mode And for the end-user view­ing any of these sites, we want­ed to support:

  • A min­i­mum col­or con­trast ratio of 4.5 for acces­si­bil­i­ty in menus and design com­po­nents but a high­er con­trast for main content

  • A dark mode that responds to a viewer’s sys­tem pref­er­ence but can also be over­rid­den with local site controls

Here’s how we were able to achieve those goals.

One (H)JSON file to rule them all #

We decid­ed that our sin­gle source of truth need­ed to be in a flex­i­ble and sim­ple for­mat. JSON fit our needs the best with its abil­i­ty to sup­port nest­ed rela­tion­ships and arrays. The only thing it didn’t allow was com­ments, which can add leg­i­bil­i­ty and doc­u­men­ta­tion. We found that HJSON was a great com­pro­mise, and used Gulp to con­vert our mas­ter HJSON file to JSON as part of the build process1.

The HJSON file is one large array. Col­ors are defined one lev­el deep along­side themes, which are also one lev­el deep. The first lev­el of the struc­ture looks like this:

  "colors": { … }
  "themes": [ … }

Col­or Def­i­n­i­tions #

Sim­ple so far. Inside the col­ors array, indi­vid­ual def­i­n­i­tions are struc­tured as a sin­gle-depth array:

    # Medium blue
    "ocean--dark": {
      "name": "Ocean State dark",
      "hue": "medium blue",
      "hsl": "hsl(208, 12%, 32%)",
      "needs": "light-text"
    "ocean": {
      "name": "Ocean State",
      "hue": "medium blue",
      "hsl": "hsl(208, 54%, 73%)",
      "needs": "dark-text"
    "ocean--light": {
      "name": "Ocean State light",
      "hue": "light blue",
      "hsl": "hsl(208, 58%, 92%)",
      "needs": "dark-text"
    "ocean--trans25": {
      "name": "Ocean State 25% transparent",
      "hue": "medium blue",
      "hsl": "hsla(208, 54%, 73%, 0.25)",
      "needs": "dark-text"

There are 46 col­ors total, but they all fol­low this pattern2. The first key is the name of the col­or, writ­ten in a slug form that will work in Sass and Twig. We like BEM, so the nam­ing of our col­ors fol­low a sim­i­lar idea. We tried to keep nam­ing things easy, so once a col­or name is estab­lished, its vari­a­tions are “ — dark­er”, “ — dark”, “ — light”, with some col­ors using vari­a­tions like “ — bright” or “ — trans25”.

With­in each col­or def­i­n­i­tions are the fol­low­ing bits of data:

name: A human-read­able name that can be used in a select list hue: How the col­or might be described hsl: The actu­al col­or def­i­n­i­tion in either the HSL() or the HSLA() col­or space needs: What col­or text would this col­or need? Light or dark are the val­ues we expect here Out­side of Pat­tern Lab, the col­ors in our sys­tem are rep­re­sent­ed by this pre­view from our documentation:

The color system as envisioned during the design and theme exploration phase