The dark mode, also called night mode, is a special color schema which uses light-colored text on dark backgrounds. The opposite of dark mode is the light mode which is or was the de facto default schema.
One of the first websites to offer an optional dark mode was Discord in 2015. Microsoft and Apple added a dark mode for Windows 10 and macOS Mojave in late 2018. In September 2019, the amount of Discord users on the light mode was under 5 percent1.
There are more benefits of the dark mode than you think. It is not just a nice styling of the user interface, but also has effects on the human body and the power consumption of newer devices.
The effects of dark mode on visual fatigue and acuity can be enormous.
A study2 found that participants had a significantly higher visual acuity for the dark mode than the light mode. They were able to complete significantly more rows on the visual acuity test chart without errors for the dark mode.
Also, the visual fatigue of the participants was significantly lower for the dark mode than the light mode.
The participants also had a clear overall preference for the dark mode over the light mode which matches the preference of the Discord users. So maybe we should implement dark mode first?
However, the preference shifted towards the light mode for situations with even more environmental light.
The benefits of lower power consumption does not apply to all devices. Since all of the pixels in a LED display are illuminated by one LED backlight, these displays cannot benefit of a dark mode. The exception is the OLED display technology where each pixel provides its own illumination. An extreme test by appleinsider.com3 has shown that an OLED display used with dark mode can save up to 60 percentage battery over 3 hours.
Current devices with an OLED display for example
All these effects will not make a big difference unless more and more apps and websites will have implemented a dark mode - especially those we use more often. So it is up to us developers, designers and project managers to drive the dark mode forward.
There is more than one way to implement a dark mode color schema for your website. Let's get through some fundamental stuff and put everything together later.
A user can select his preferred color schema in the system settings of his operating system. This setting can be read by all modern browsers4, and we can query this setting in CSS via a media query.
/* user prefers dark mode */
@media (prefers-color-scheme: dark) {
body {
color: #f0f8ff;
background-color: #363636;
}
}
/* user prefers light mode */
@media (prefers-color-scheme: light) {
body {
color: #363636;
background-color: #f0f8ff;
}
}
A very bright image on a dark background can be annoying for its viewer. So why not decrease the brightness of the images with the CSS filter? The contrast ratio may be also important.
/* user prefers dark mode */
@media (prefers-color-scheme: dark) {
img {
filter: brightness(.9) contrast(1.1);
}
}
/* user prefers light mode */
@media (prefers-color-scheme: light) {
img {
filter: brightness(1) contrast(1);
}
}
Here is an example comparison between dark and light mode with an image with the above settings.
If you were presenting photos on your website for which the colors and edit were important, maybe it will not be a good idea to alter its brightness
Like other media query change events, you can subscribe to a change in the preferred system color schema via JavaScript.
This change can be triggered by the user or automatically by the operating system based on the time or illumination detection for example.
const prefersColorSchemaDarkMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
prefersColorSchemaDarkMediaQuery.addListener((e) => {
const darkModeActive = e.matches;
// do something when dark mode has changed
});
When a user is not able to use a modern browser, or she wants to switch to the opposite mode without opening the system preferences
every time, a manual switch should be implemented. This is useful when a user for example prefers the dark mode but is
in an environment with more light. The user can switch to the light mode, so she can interact with the user interface in a better
way.
This can be implemented with a generic dark mode class which gets attached to the body element.
/* default light mode */
body,
body.colorSchemaLight {
color: #363636;
background-color: #f0f8ff;
}
/* class for dark mode */
body.colorSchemaDark {
color: #f0f8ff;
background-color: #363636;
}
When a user switches the mode manually, this setting should be saved in either a cookie or the local storage for example. Now it is your choice which setting should have a higher priority. I would suggest that the manual preference should have a higher priority than the system preference because this was chosen by the user intentionally.
For the complete implementation strategy of the CSS, you must decide which way is better depending on your target group. Semantically, I would prefer the usage of CSS variables. Also this would be much easier to implement. But since some older browsers do not support CSS variables4, I will also show you a way which is also good to maintain with the help of SCSS.
:root,
:root .colorSchemaLight {
--background-color: #fff;
--text-color: #202020;
}
body .colorSchemaDark
{
--background-color: #202020;
--text-color: #fff;
}
/*
We must copy the above code because
media queries cannot be combined
with selectors
*/
@media (prefers-color-scheme: dark)
{
:root {
--background-color: #202020;
--text-color: #fff;
}
}
You can now use the colors as followed:
body {
background: var(--background-color);
color: var(--text-color);
}
// define color schemas
$colorSchemas: (
light: (
background: #fff,
text: #202020,
),
dark: (
background: #1f1f1f,
text: #bfbfbf,
),
);
/// get color by schema from map
///
/// @param {string} $colorName
/// Color name from $colorSchemas
/// @return {string} hex color by colorName.
@function getColorForSchema($colorName) {
$colorSchema: map-get($colorSchemas, $colorSchemaName);
$color: map-get($colorSchema, $colorName);
@return $color;
}
/// mixnin to fill CSS content
///
/// @param {bool} $isBody
/// If mixin is used in body element
@mixin useColorSchema($isBody: false) {
$colorSchemaName: 'light' !global;
@if $isBody {
&,
&.colorSchemaLight {
@content;
}
} @else {
&,
.colorSchemaLight & {
@content;
}
}
$colorSchemaName: 'dark' !global;
@media (prefers-color-scheme: 'dark') {
@content;
}
@if $isBody {
&.colorSchemaDark {
@content;
}
} @else {
.colorSchemaDark & {
@content;
}
}
$colorSchemaName: null !global;
}
You can use the mixin useColorSchema by this:
body {
@include useColorSchema(true) {
background-color: getColorForSchema('background');
color: getColorForSchema('text');
}
}
Which generates following CSS:
body, body.colorSchemaLight {
background-color: #fff;
color: #202020;
}
@media (prefers-color-scheme: dark) {
body {
background-color: #1f1f1f;
color: #bfbfbf;
}
}
body.colorSchemaDark {
background-color: #1f1f1f;
color: #bfbfbf;
}
The negative impact of the implementation via SCSS and a mixin is also that more CSS code will be generated.
If you were using the modern frontend framework React, you can use the use-dark-mode component which handles the logic, so you will only have to implement the (S)CSS part.