VOOZH about

URL: https://bjango.com/articles/whentwocolourscanbeone/

⇱ When two colours can be one


Apps Articles Help Contact

When two colours can be one

Many user interfaces have dark and light themes. Having a dark and light theme usually means defining a full set of colours, and switching them when the theme switches.

👁 Image

Sometimes you don’t need to switch all the colours though. With some careful selections, just changing the background colour can change the theme.

👁 Image

You can probably guess what’s going on — I’ve used a mid grey, with varying levels of opacity to blend into the background as needed. We can not only use the full strength mid grey, but also a 50% opacity version that automatically becomes lighter when on a white background, and darker when on a black background. The technique has limits, but it’s a nice trick.

If you have specific colours in mind, it would normally take a lot of guesswork to find the ideal colour and opacity level needed for both themes. It would be better if it was possible to find the colour and opacity in a more deterministic way.

Determining the colours #

Let’s start with a 10% dark grey and 90% light grey for the theme backgrounds and assume we are trying to find a colour and opacity combination that will give us a 40% grey for the dark theme and a 60% grey for the light theme, as the final composited result.

The formula for what is typically referred to as “normal blending” is foreground × foreground_opacity + background × (1 - foreground_opacity). Yes, I’m assuming an opaque background. Everything below is worked out in normalised space, where 0 is the minimum and 1 is the maximum, rather than 0 to 255 or 0% to 100%. We’re also just working in greyscale, to keep things simple for now.

Plugging in the values we know for the dark theme into the formula, we can graph the possible values for the foreground colour (using the X axis) and the possible values for the opacity (using the Y axis).

👁 Image

All the colour and opacity combinations along this curve give the result of 0.4, if the background is 0.1. Rather than blending a forground and background colour to find out the result, we’re doing the opposite — we’re taking a result we want, and calculating the forground colour and opacity.

👁 Image

Now let’s plug in the values for the light theme. The resulting curve works the same way — all the colour and opacity combinations along this curve give the result of 0.6, if the background is 0.9.

👁 Image

Overlaying the graphs for the light and dark theme colours shows they intersect at 0.5 on the colour (X) axis and 0.75 on the opacity (Y) axis.

👁 Image

A colour of 0.5 with an opacity of 0.75 will result in a final blended colour of 0.4 on a background of 0.1 (dark theme), and it will also result in a final blended colour of 0.6 on a background of 0.9 (light theme).

That’s the magic colour and opacity combination that gives us the result we want for both the dark and the light theme. We can use a single colour for elements for both themes, and just change the background colour.

What if we wanted to change the colour for the light theme, so it’s 0.5 grey, but keep the dark theme as is, at 0.4? Adding another curve for it shows the intersection has moved to a colour of 0.46 with an opacity of 0.8.

👁 Image

It’s possible to use any grey for the light theme, provided it isn’t darker than 0.4.

👁 Image

Other blending modes #

The same technique can be used for other blending modes. If you have a desired final colour and background colour, a curve of possible foreground colours can be worked out.

Performance #

Is this useful? Maybe. Maybe not. Writing code to switch colours when a theme switches isn’t difficult, and it’s more flexible than the technique detailed above. I see it more as a fun experiment, rather than something that would be used in production.

However, there might be performance reasons for wanting to work this way — changing just the background colour could save on layer repaints, which could be important if you’re using a lot of text or animating colours.

Published 31 December 2016.

Featured articles

The vocal effects of Daft Punk

Matching drop shadows across CSS, Android, iOS, Figma, and Sketch

Two decades of Bjango

Design systems need a colour space

Blur radius comparison

Design tool canvas handles

Design tool memory usage

Shape builder vs pathfinder

Formulas for optical adjustments

Smaller Mac app icons

Icon speedrun videos

Fingerprint icon speedrun

Fountain pen icon speedrun

Pushpin icon speedrun

Flag icon speedrun

Designing macOS menu bar extras

SVG passthrough precision

Ideal SVG exports

Mac external displays for designers and developers, part 2

Camera iris icon speedrun

Opacity precision

Design tool performance signatures

Diagnosing common colour management issues

macOS prefs tab icons are 27×27pt

What is pass-through blending?

Colour management, part 4

Magnet icon speedrun

Soccer ball icon speedrun

Puzzle icon speedrun

Using SVGs in asset catalogs

My Illustrator snapping settings

Perfect loops in Processing

Testing for wide gamut

Creating SVGs with Processing

Colour management, part 3

Colour management, part 2

Colour management, part 1

Vector icon speedruns

My Mac app icon design workflow

Color Creator Templates

When two colours can be one

Mac external displays for designers and developers

Greyprint

Batch processing with Generator

Using blend if for masking

Design debugging

Interaction density

1px is not enough

My icon design workflow

Why Skala won't have artboards

Why I don't use PDFs for iOS assets

Blend if

Gradient maps

About the author

Marc Edwards is the founder and designer at Bjango. Subscribe via RSS to be notified of new articles. Marc on Mastodon. Marc on Bluesky.

We’re building a design tool! Learn more about Skala.