Creating a Dark Theme for a Web App

A lot of websites and apps eventually roll out a dark mode – it’s not exactly a unique challenge, but it is a challenge, nonetheless. It would be nice if you could just flip-flop every color in the UI: that orange with a brightness value of 90%? Now it’s 10%. Whites are now black and vice-versa. But the fact is that there’s a lot of adjustment and tailoring that needs to be done to ensure the interface remains functional, legible, and that the different elements maintain the correct amount of focus and attention they need.

The first step I took was to experiment with a few pages that I already designed out for light mode. I tried to pick ones that had good examples of color, complexity, and diverse elements. I was not checking contrast ratios or running anything through a color-blindness simulation at this point – I just wanted to see roughly what was working and what was not.

I was able to tell fairly quickly what would not work. Cards that were darker than the page background receded too much and looked like cut-outs of the page. My brief experiment with keeping cards and buttons white was also a mistake – they bring way too much attention to themselves and feel out of place.

The key was to iterate quickly through these and “fail fast.”

Early versions of the dark theme.

Once I had some examples that showed promise, I started taking the color values I used in the tests and creating variables for them. This consists of a primitive group of colors that represents all the colors in the UI, and a semantic color set that references the primitives and is named based on the context its used in. This gives us a clear view of each different color values within the website and separates it from their use.

Primitives are assigned to both a light and dark version of each semantic color. Sometimes the light and dark theme use the same color as in “icon-black” below, while other times different primitives are used for light and dark, such as “icon-contrast.” Referencing the primitive color variable instead of recreating it also has the advantage of creating a single place to update the value if it should ever need to change.

Figma color variables
Color variables in Figma. Primitive colors on the left are assigned to variables with semantic names on the right.

Once that was done, it was time to flip every page over to dark mode and check for any problems. If I saw areas of low contrast I would either adjust the primitive value or assign a different primitive to the semantic color and run a test. Since this is a live document, and I’m always adding pages and features, I continue to adjust and check.

Screenshots showing the original light mode and the new dark mode.
Original light mode on the left, new dark mode on the right.