Dark color Things you should know about color when adopting dark mode.

Before we begin, I encourage you to visit my previous article Adopting ios Dark Mode to get a basic idea of what is dark mode and tools Apple bring with it.

Adopting dark mode is all about setting a new set of colors.

For most apps, adopting dark mode is a process of replacing your static color to a dynamic one.

Let's say your app has a white background, and you want to adopt dark mode. You open up Asset Catalog, create a new color set, and put white and black colors for each appearance. You might end up with something like this.

Use hard-coded black as a dark appearance.

Which translate into something like this.

UIColor { ( trait ) - > UIColor in

switch trait . userInterfaceStyle {

case . light :

return . white

default :

return . black

}

}

Run it and everything looking good, but actually, it is not. This should be a straight forward process, but there are a few things you should know when replacing your colors.

Dark mode is not just a light and a dark color #

If you try to present a modal with the color we just created, you will see something like this.

It looks beautiful in the light, but let's try that in the dark.

As you can see, the drop shadow that the system applies to differentiate the floating visual can't convey the same message in the dark.

There are no shadows in the darkness

How is the system color solving this? If you are running the same code using systemBackgroundColor instead of .black . This is what you will get.

UIColor { ( trait ) - > UIColor in

switch trait . userInterfaceStyle {

case . light :

return . white

default :

return . systemBackgroundColor

}

}

The presented view become gray to make it possible to distinguish between a presenting and presented view.

It turns out that system color doesn't just take light and dark into consideration. It also considers the elevation level.

Elevation Level #

From what I remembered, Apple first mentioned the depth in their human interface guideline when they introduced iOS11 with flat design. In iOS13, they brought that concept to the trait collection .

Levels create a visual separation between different parts of your UI. Window content typically appears at the UIUserInterfaceLevel.base level. When you want parts of your UI to stand out from the underlying background, assign the UIUserInterfaceLevel.elevated level to them. For example, the system assigns the UIUserInterfaceLevel.elevated level to alerts and popovers.

– Apple Documentation

UIUserInterfaceLevel got two cases right now, base and elevated . Modal presentation pass elevated in a trait collection to a presented view controller. UIUserInterfaceLevel is what the system color uses to determine whether it presents as modal in dark mode or not and adapt the color accordingly.

I can't find a way to do this in Asset Catalog, but in code, it would look something like this.

let customBackgroundColor = UIColor { ( trait ) - > UIColor in

switch ( trait . userInterfaceStyle , trait . userInterfaceLevel ) {

case ( . dark , . elevated ) :



return . gray

case ( . dark , _ ) :



return . black

case ( _ , . elevated ) :



return . white

case ( _ , _ ) :



return . white

}

}

The elevation level is just one aspect of dynamic color. I use it as an example to give you a hint of how deep this can go. More aspects needed to be considered, e.g., contrast, font size, font-weight . We won't go into detail in this article.

As you can see, .systemBackgroundColor do all the hard work for us to making sure everything looks great in both light and dark mode.

Rule of thumb #

Here is my rule of thumb when adopting dark mode.

Use system colors if possible #

As you can see, the system color you see isn't just two colors, light and dark, pack into one. There is more to it than meets the eyes. By using system colors, you get all those benefits without sacrificed anything. So have a look at the color Apple provided and try to use it as much as possible. If you have a color that slightly different from system color, it always worth using the system one.

If there is no obvious mapping, override the only trait you need #

You started with system color and modified just the trait you need. Let's say you have your custom background color in your app and want to support dark mode. I would do something like this.

let backgroundColor = UIColor { ( trait ) - > UIColor in

switch trait . userInterfaceStyle {

case . light :



return . fancyColor

default :

return . systemBackground

}

}

Make sure your color look good in light and dark #

If you can't convince a designer, pm, or yourself to use system color, make sure you visit Human Interface Guidelines - Dark Mode and Color and Contrast.

Apple makes the process of creating custom dynamic colors effortless and pleasant, but don't let the easiness lure you into doing so.

As you can see, there are a lot of things to consider when introducing a new color to your app. Create a custom one mean you are giving up all the benefit and works Apple done for you in system colors.

If you don't have a large design team dedicated to this, my suggestion would be to stick with system one.

Related Resources #

Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.

If you enjoy my writing, please check out my Patreon https://www.patreon.com/sarunw and become my supporter. Sharing the article is also greatly appreciated.

Become a patron

Tweet

Share

← Home