Screenshots of where the Freetrade app started.

Creating colour systems

and shipping dark mode 👀 🌘

As we scaled our team at Freetrade, we began having conversations around when it was right to use certain styles, colours and patterns and although we had a decent component library, this only existed in our design tooling. As a result, much of the rules and re-usability was being lost in translation from design to code.

This was then compounded by having no clear palette in our codebase which would often lead to the creation of new colours to fit each project:

10 different greys in an app that was only ~10 months old.

Because of this, I decided to codify the rules we’d kept in our heads and create a single source of truth™ for both design and engineering. But, before solving for this I first — as with all things — needed to identify the cause of the challenges:

  1. Our colour palette was unclear
    From the list above, any idea which of those is the correct one to use for grey text? No, me neither. And, yet, on each project, the teams were required to pick from that list and hope for the best.
  2. Our rules weren’t understood
    Rules are only rules if they can be understood and enforced and so if the team don’t fully understand the “rules” we have, or we don’t have the tooling to enforce them, then they don’t exist.

Fixing our palette with design tokens 🎨

A design token is a named entity that stores a piece of visual design information such as colour, spacing, border-style or type size. As an example, take this text:

Left: Text link shown using color: #F7618B. Right: Text link shown using color: freedom pink. With the same results.

It’s comprised of many tokens but, let’s focus on colour. So, Instead of using color: #F7618B you would have color: freedomPink. Whilst visually this produces the same result, with the tokenised value you know what you’re going to get.

Getting started

I began by creating tokens for all of our palette then, using Theo (a tool from the team at Salesforce) we would transform their values to the formats we use on iOS and Android. Once transformed our tokens would then be run through a custom format that we had created to spit out two files, colors.swift for iOS and colors.xml for Android.

Now, here’s where things get fancy 🎩

These files would then be included in the app so that they can be referenced when building UI. What makes this approach exciting (at least for me) is a couple of things:

  1. It makes it easier to use the colour specified in the designs
    Since now all colours are pre-defined and named in human-readable ways we don’t need to pull out the colour picker or create something new. This minimises mistakes made when using colours.
  2. Colours are centralised, making changes easier
    Colours are stored in their own file. When we come to tweaking or changing one that change will be applied to every instance in which it was referenced.
  3. It removes a barrier for non-technical members of the team
    With the colour values now separated from the functional code of the core product, things get a whole-lot-less intimidating. People can now update and add colours without fear of breaking anything.

Creating rules 👮‍♀️

As I was working on this Apple announced system-wide support for dark mode. This would add complexity to our colour palette and additional logic to factor in.

So, to understand how this would affect us we first tried to just invert everything to see how that felt and for the most part it was pretty good but some of the main things that didn’t work were:

Shadows and dimmers no-longer provided visual separation
We frequently use shadows or dimmer layers to separate an element from its background. However, shadows just make black a little more… black.

Left: Card in light-mode differentiated from the background. Right: Card in dark-mode without differentiation.

Our brand palette had been created for a light product
When we put our light brand colours on a dark background they felt out of place and didn’t evoke the same feeling as in a light theme.

Example of existing brand colours not looking correct in dark mode.

So, how do we fix these? 🔨

Given the initial challenges we had with the team’s uncertainty around when to use certain colours or patterns, combined with the needs introduced by the addition of dark mode, we decided to combine both colour and use case into what we would call a declarative token.

This meant we could smartly invert our interface, for example: having tokens for both screen background and card background.

The side effect of this change?
This introduces many, many more colour tokens than before. So, to ensure we don’t begin to recreate the issues that brought about this project we would still maintain our palette as aliases i.e. Freedom Pink, Indigo, Green, etc.

And, they would be pulled into our declarative colour tokens.

You can see here that when we set the value of card_default_light we reference White from the palette file.

This is looking pretty good, but it’s not yet in the correct format. What iOS requires is a single colour string for an element. To get to this we adapted our existing custom format to combine the light and dark variants of tokens into a single string that looks like this.

So that now, when we apply card_default in our iOS app, we’ll get a result that’s applied differently in both light and dark mode.

Left: Card on white. Right: Adjusted card on black.

Next, we needed to get the colours looking right. In most cases where we’d had issues, we found there wasn’t a single colour that would give us a colour contrast rating and visual look and feel we wanted in both light and dark mode. So, to make this work we needed to be able to provide both light and dark variants of our colours.

To do this we take a palette token, for example, apricot20 - our lightest shade of yellowish-orange - create a second version and append _light and _dark respectively.

Left: Brand colours on white. Right: Adjusted brand colours on dark.

When a dark mode variant isn’t provided it will fall back to just using a single colour for both light and dark values.

Whilst this solution doesn’t provide the cleanest code it is very explicit and doesn’t leave much room for misinterpretation which is exactly what we want to avoid.

So, where are we now?

Our colours and rules are now coupled in code and when it comes to styling an element e.g. a card, instead of specifying the exact colour we just have color: card_default which makes it easy to know which style to use reducing the likelihood that we’d make a mistake and saving everyone a bit of time.

This also makes it simple for the design team to go in, tweak a colour token and have that change apply throughout our apps, across multiple platforms without needing to take up engineering time.

On top of all that, we have a lovely new dark mode to demo all this work.

Gif: Transitioning between light and dark mode.

Consumer Design Lead @ OakNorth

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Realm Runway #1: New York City. Feb 11th 2022.

Digital design during the 2020 pandemic: new challenges and new opportunities

The Best Drawing Apps for iPad and iPhone in 2019 — Art & Inspiration

Nurturing Creativity

Non-Linear: Organic Community

The Pleasures of Good Sound — 5 tips for Improving Home Acoustics

Acoustically optimized speaker

What Happened at Inventure$ 2019: Calgary’s Innovation Conference

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Mitchell Petrie

Mitchell Petrie

Consumer Design Lead @ OakNorth

More from Medium

Nike Run Club — Export Feature

I made a plugin called “figmark” that allows you to bookmark your favorite layers in Figma!

How Libraries work on Figma

My Top 3 Intriguing Features in Figma (a.k.a my Figma Superpowers)