Dark Modes

A discussion about dark modes and challenges encountered in their design

Cover Image

Dark mode has now become a staple in modern interfaces. Mainstream apps, government websites, and operating systems all have dark modes now, and I prefer them over the harsh white, however ugly they may be.

Dark mode is a different stylistic version of the same interface, and uses lighter colors on top of darker background. We expect no change in layout. Any changes that could be done to the layout should work in both modes.

But should everything need a dark mode?
And can everything be turned dark?

Is it needed?

Dark mode is cool’n’all but does it make sense in your situation? The question we must understand is whether mode switching is necessary or not. Whether a single mode, light or dark, makes more sense than adding the option to switch modes.

Whether one should have a dark mode depends on what one wants to show, i.e. it depends on the content being shown. And to whom it is being shown to, and for what purpose. (The context) And we should consider both while deciding whether dark mode(s) is needed.

I consider frequently used applications and apps that focus on the content ideal for dark modes. Applications are opened more frequently and people spend more time with their interface than normal websites. Complex editors - both text and graphic - want to focus on the thing being edited rather than the editor’s interface itself; and dark mode succeeds in fading the interface away.

There is always a tug of war between usability and uniqueness. The more the interface looks and behaves like every other interface, the more usable it will be, but the less unique. A usable interface shall remain usable in dark mode as long as the contrast and legibility are cared for.

Dark mode examples in websites

Sometimes dark mode can become a brand identity.

Additionally, dark mode allows for the possibility of high contrast modes.

There are arguments from research about how the black on white background (light mode) is easier on the eyes for reading, but in my case, I have developed a fondness with dark mode after several years of usage. And hence I like to interact with interfaces in dark mode.


The conversion

After considering the content and the context, if dark mode is indeed what we want, we can begin our journey in converting our interface to a dark mode.

Initially we can be tempted to use one of the most common methods of converting a light mode to a dark mode. Simply switching the background and text colors. Here is a rudimentary example:

Black & White
Black letters look thin as white light bleeds into the black lines, also things appear light and airy
Black & White
White letters bleed light onto the black background making text appear thicker. Consider also that this black is currently inside a dark blog.

While this suffices for basic text, there is a large variety of elements and layouts that will not look good with this simple flip. For example, if we have one color for text and another for headings in light mode, what color should the background be in dark mode? We have to consider how colors change in dark mode, and what we want our surfaces to look like.

Colors

Although an explanation about how the perceived colors depend on the background is not needed, here is a fine example:

Notice the color blue
This is our benchmark
Now a direct conversion
See how this doesn't match
Even though its the exact same hex code!
A little adjustment
To the color makes it match the color in light mode.

Colors in the light background look different when shown with dark background. Notice how the contrast and saturation change when we flip the default colors of Bootstrap:

#0d6efd
primary
#6c757d
secondary
#ffc107
warning
#dc3545
danger
#198754
success
#0dcaf0
info
#0d6efd
primary
#6c757d
secondary
#ffc107
warning
#dc3545
danger
#198754
success
#0dcaf0
info

Sometimes retaining these slight mismatches may be what is desired, but we can tweak the colors to suit each other and the background.

Tweaked themes work well together and still retain contrast to differentiate with color. Color should not be the sole way of differentiation as it would not be legible to the visually impaired. Moreover, anything that makes things more legible is good for everyone.

A few examples of color palettes that work well together:

#ff5370
primary
#6b5eff
secondary
#CCA685
heading
#CC64AD
italic
#919DCF80
fadeText
#919DCF
text
#FBA359
primary
#B8BB26
secondary
#88B877
heading
#F47F71
italic
#897969
fadeText
#AEA398
text

The first set of colors above are the ones currently being used on this website, and the second are chosen from the Gruvbox color scheme. Similar harmony of colors is also used in choosing the color palette in Material Design 3.

The ugly gray

One of the easiest ways to make a dark mode color palette is to pick shades of gray. However, the interfaces that use such gray shades look dull and washed out. Desaturated. As if someone disabled the app itself instead of a button. Especially if combined with material design (v2).

Gray is static, gray is ugly
Gray on gray is uglier still
Gray is grey, gray and darkgray and lightgray and dark all at the same time.
A little darker?
We might as well have used black
And the surfaces still look ugly
Adding a bit of color
Adds a lot of life that was missing
In these drab slabs of gray
And we can always go darker still
Though the effect is not as pronounced as saturation is low
Yet it is good enough

And I think these gray themes are what have given the dark mode the aura of unsaturated monochromatic soulless interfaces. As if colors are meant to be reserved for the light mode. Adding a hint of color makes them look more vibrant and saturated.

Colors that were lost

Some colors look bad on white background if used as text. Yellow is a prime example. But in the world of dark mode, yellow literally shines!

Yellow in dark mode is more readable
than the light mode
Yellow in dark mode is more readable
than the light mode

Another example are the headings here in the blog, the color would just not work with light mode.

Surfaces

Another dilemma arises when we have a background and a card, there are several ways to differentiate the background with the foreground.

In the light mode, we can enjoy a variety of styles to separate content into boxes.

A shadow on a box is universally exploited as a card. It adds the perception of depth.
A lighter background can look as if it is ahead of a darker background even though there are no shadows.
A darker background can mean anything, from simple info to notifications, buttons and disabled elements as well.
A border can be a box if the design system decides it is so, and it can also be used as input.

Shadows

Natural looking shadows are easy in light mode as there is ample contrast from the lighter background for a faint shadow to be visible, however in dark mode, the shadow opacity and darkness has to be turned all the way up. Sometimes a glow is added instead as it matches the dark metaphor.

This shadow looks fine, like a normal card would make.
Notice how the same shadow is barely visible in dark mode
But once we pump up the shadow opacity we get similar contrast.
A bit of glow, but it can sometimes become too harsh.

As we cannot rely on shadows, having a contrast difference between the background and the foreground can serve to differentiate content. However, should the foreground be lighter or darker?

While it is indeed true that life is hard as evidenced by punching the ground ....
While it is indeed true that life is hard as evidenced by punching the ground ....

Lighter surfaces simulate as if a light source is above the screen lighting it from atop, and the surfaces further forward are shaded lighter. For multiple stacked surfaces we can make the surfaces above lighter than the ones below. Do not add additional surfaces only for dark mode.

Shiny

A shiny look can be implemented by replicating the characteristics of the light reflections via a gradient border and background. An example of the current shiny look:

AI Beast
The beast machine that can determine the fate of AI in the future of large language models and cloud compute.

The code block

While styling basic components is straightforward, there are elements that have a large number of colors, take the code block for example:

// Just some jsx to preview the theme!
/**
 * Shows a single theme prevw
 * Requires theme vars: `Themevars`
 */
export const ThemePreview = (props: ThemePreviewProps) => {
  const themeCssVars = flattenObject(props.preview.vars ?? lightMode, (keys, value) => [
    `--preview-${keys.join("-")}`,
    value,
  ]);

  return (
    <Container>
      <Column style={{ ...themeCssVars }}>
        <For each={props.preview.elements ?? themePreviewElementConfigs.minimal.preview.elements}>
          {(element) => (
            <RenderElement element={element} />
          )}
        </For>
      </Column>
      <Show when={props.showInfo === undefined || props.showInfo}>
        <ThemeInfo {...props} />
      </Show>
      {props.children}
    </Container>
  )
}

In the days before mode switching, code blocks had a dark color scheme in light modes. Should light mode and dark mode both have different color schemes? Does this mean we add a light code highlighting theme for dark mode? Nope.

Chances are you are using a popular color scheme, thus it would be available/convertable to the code highlighting solution you use. If not so, picking from one of the default color schemes also works. Go for a specific customized version if it is important, and make sure that the color scheme remains consistent across the experience.

Consider one of the classic dark mode themes: Tomorrow Night.

/** A function to end all functions */
function showcaseSyntaxHighlighting<T extends string>(
  name: T,
): void {
  // Use a for loop to log the age multiple times
  for (let i = 0; i < 18; i++) { console.log(`Age marker: ${i}`); }
  interface User<T> {
    name: T;
    age: number;
    isDeveloper: boolean;
  }
  const user: User = { name, age: 18, isDeveloper: false };
  showcaseSyntaxHighlighting('Alice');
}

Some may want a custom theme that matches their overall style, so they may want to create a new theme or tweak an existing one. However it is not an easy affair, we have to create a custom theme for the highlighter that is being used. For which we usually find a theme we derive from.

As an example, this blog uses astro which comes with shikijs/shiki syntax highlighter which can take a textmate or vscode highlighting config and use it to highlight code in html. However, due to the inherent complexities of the structure of theme, modification is non trivial. While I was tempted by it’s css-variables color theme, it doesn’t work as nicely with complex code snippets such as jsx. I ended up writing/tweaking the default vscode dark theme to match my color scheme.

Perhaps our optics desire a more lively look and we like lavender and pink as our main colors. A few variations of such custom theme could be:

/** A function to end all functions */
function showcaseSyntaxHighlighting<T extends string>(
  name: T,
): void {
  // Use a for loop to log the age multiple times
  for (let i = 0; i < 18; i++) { console.log(`Age marker: ${i}`); }
  interface User<T> {
    name: T;
    age: number;
    isDeveloper: boolean;
  }
  const user: User = { name, age: 18, isDeveloper: false };
  showcaseSyntaxHighlighting('Alice');
}
/** A function to end all functions */
function showcaseSyntaxHighlighting<T extends string>(
  name: T,
): void {
  // Use a for loop to log the age multiple times
  for (let i = 0; i < 18; i++) { console.log(`Age marker: ${i}`); }
  interface User<T> {
    name: T;
    age: number;
    isDeveloper: boolean;
  }
  const user: User = { name, age: 18, isDeveloper: false };
  showcaseSyntaxHighlighting('Alice');
}

Icons & Illustrations

Icons and Illustrations add visual appeal to an interface and help users navigate & understand by adding meaning via graphics.

Simple icons and illustrations can be directly converted to dark mode by inverting the colors and tweaking them to suit the needs. For icons, a icon-font or svg icons (ex: iconify.design) can be adjusted via the text color. Illustrations in vector formats also lend themselves to tweaking the colors for the dark mode.

But it should be noted that not all illustrations and icons are fit for direct conversion.

In case of icons, we can take the phases of the moons for example. A standard icon for the new moon is one of a dark moon with no light. However, it doesn’t translate to dark mode as the meaning of the colors to represent the phases is also inverted. In such a case, using a different set of icons corresponding for different modes works well. The second variant is a simple flip of colors, the third variant has the icons shifted by half the length so that the icons match with their intended meaning with light mode.

For illustrations, we should be mindful of the backgrounds, and the contrast between the elements and the background. For example consider these illustrations from undraw.co:

Here the first variant is the illustration in light mode. Notice how changing the background to black makes the shape stand out when it is not necessary. We can adjust the shade of the shape to match the dark mode, and make the steps lighter for better contrast.

Here the conversion results in poor contrast of the legs and the desktop with the background and the less desirable high contrast of the table. Tweaking these colors helps us make the illustration somewhat legible again. Also don’t forget turning the interfaces inside the illustrations to dark mode as well!

Contrasty design

There are designs (of websites, products) that use different modes (dark/light) in the same interface to distinguish a sidebar/navigation/sections of a page.

For example consider the alternating sections of a website:

These split sections
have a lot of clear separation
The separators
Are particularly legible
Nice it is that
And the content is marked
These split sections
Will look generally the same
In Dark mode
As the reasons for night usage and content focus have been abandoned.
But at least it is unique

These split sections are a stylistic choice and would look somewhat similar in both modes. The decision whether to flip them individually or let them remain in their original modes is entirely subjective.

Sidebars are another example of using two modes in the same interface. Generally the sidebar is dark and the main content is light mode.

The Main Section
contains the content on the page, this is usually the dashboard of the application
It's light color
Serves as ample contrast for the sidebar in dark background beside it.
However one should also consider that both just light and just dark sidebars are possible

While this is effective to some degree. It is neither dark nor light. And the contrast is needless in most cases.

And heaven forbid if one considers changing the color of the sidebar equates to dark mode.

One can have similar interfaces in all dark mode:

The All dark
The Main Section
looks like it is dark this time, and the sidebar as well?!
The dark colors
Are a little toned down.
Though there is less contrast, the sidebar looks like a cohesive part

Thus the choice of using split modes in interfaces becomes a subjective choice and is left to the tastes of the builder.


Dark modes with different colors, one pink, the other green and last red.

With the fundamental surfaces and colors sorted, and the edge cases considered, we can build dark modes in interfaces. Do note that this does not consider layout which is independent of dark mode. I will discuss layouts in a different post later.


Resources

Source Code


The Notes series will be revived next.

Sooner or later, everything ends.

Comments