Two lines of CSS to easily implement light and dark modes

2024.02.20

In web development, in order to adjust the color of web pages according to the user's preferred mode (light and dark mode), we may have used media queries (prefers-color-scheme). Today, CSS provides a simpler method: the light-dark() function. This function can automatically select one of the two colors for output based on the current color scheme, thereby achieving adaptive display of colors.

Previous light and dark mode implementation

To change the color value based on whether light mode or dark mode is used, you can use the prefers-color-scheme media query to change the value of a custom property:

:root {
  --text-color: #333; /* 浅色模式的值 */
}

@media (prefers-color-scheme: dark) {
  --text-color: #ccc; /* 深色模式的值 */
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

When implementing dark mode, it's common to end up with a bunch of repeated CSS variables that set the values ​​for each mode. CSS will then use these custom properties for the actual declaration.

body {
  color: var(--text-color);
}
  • 1.
  • 2.
  • 3.

Future light and dark mode implementation

CSS Color Module Level 5 Specification [1] adds a new light-dark() function. This function accepts two color values ​​as its parameters. Depending on the color scheme being used, it will output the first or second color parameter.

light-dark(<color>, <color>);
  • 1.

According to the specification, this function evaluates to the calculated value of the first color if the color scheme used is light or unknown, or to the calculated value of the second color if the used color scheme is dark.

The color scheme used is not only the light and dark mode selected by the user, but also needs to be determined based on the value of the color-scheme attribute. The color-scheme attribute can indicate which color scheme the element should use for rendering. This scheme will be negotiated with the user's preferences to ultimately determine the color scheme used. Therefore, when using the light-dark() function, you also need to include the corresponding color-scheme declaration in CSS to ensure that the function can work correctly.

:root {
  color-scheme: light dark;
}

:root {
  --text-color: light-dark(#333, #ccc); 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

For the above code, the first value is returned in light mode and the second value is returned in dark mode.

Color-scheme can be set on a specific element to override the default value, forcing the element into the desired mode.

.dark {
  color-scheme: dark; 
}
  • 1.
  • 2.
  • 3.

For the above code, light-dark() on this element and its children will always return the value corresponding to dark.

Let's look at a simple example. The following demo will show several <div> elements with .auto class. These elements intelligently adapt to the system color mode, automatically switching to light or dark themes. <div> elements with .light or .dark classes will force the corresponding color mode to be applied.

#demo {
	color-scheme: light dark;
	
	.dark {
		color-scheme: dark;
	}

	.light {
		color-scheme: light;
	}
	
	& div {
		background: light-dark(#d4d4d4, #000);
		color: light-dark(#333, #ccc);
	}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

What about non-color values?

The light-dark() function was designed to provide a simple intermediate solution that only supports switching between light and dark colors, and only works with color values. This design choice was intentional as it was intended to provide a gradual transition to the final solution.

As proposed by the CSS Working Group, the future goal is to introduce a more powerful function, tentatively named schemed-value(). This function will have the following properties:

  • Ability to respond to any color scheme value, not just light and dark colors.
  • Supports multiple types of values ​​in addition to color to provide a wider range of customization options.

It might look like this:

:root {
  color-scheme: dark light custom;
}

body {
  color: schemed-value(light hotpink, dark lime, custom rebeccapurple);
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

Currently there is only the light-dark() function, but it is sufficient to cope with the actual situation of current browser functions:

  • It only supports light and dark modes, because current browsers do not yet support <custom-ident> in color-scheme, so supporting other modes is not yet practical.
  • It only handles <color> values ​​because the parser needs to know ahead of time what type of value is being parsed. light-dark() is explicitly defined to handle <color> values.

Narrowing the scope of the function from the broad schemed-value() to the concise light-dark() allows the function to work as currently defined without requiring it to be included in the long-term development track. In addition, the name and syntax of light-dark() are very easy to remember and use, and most importantly it provides developers with a practical solution that meets their current needs.

Browser support

Browser support for the light-dark() function is as follows:

  • Chromium (Blink):  ⚠️ Intent to release has been expressed. Expected to be included in Chrome 123.
  • Firefox (Gecko):  ✅ Firefox 120 starts to support
  • Safari (WebKit):  ⚠️ This feature has been implemented in WebKit in the master branch. Expected to be included in Safari TP 188.

You can use the following code to determine whether the browser you are currently using supports the CSS light-dark() function:

@supports(color: light-dark(#fff, #000)) {
	#output::after {
		content: "✅ 浏览器支持 light-dark()";
		background-color: #00ff002b;
	}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

Pay attention to browser support:

  • Chromium/Blink: Issue #1490618[2] — Started (open status)  
  • Firefox/Gecko: Issue #1856999[3] — Resolved