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.

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

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).

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.

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.

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.

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.

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

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.