WordPress.org

WordPress Developer Blog

Mastering light and dark mode styling in block themes

Mastering light and dark mode styling in block themes

One of the challenges I’ve wanted to tackle for a while is creating themes with both light and dark modes. There are many methods that developers have used to do this throughout the years, some more complicated than others. But I laid out a few ground rules for myself when stepping into this challenge:

  • The solution must respect the site visitor’s operating system preferences.
  • The theme user shouldn’t have to take any steps to make it work.
  • The code must live in theme.json as much as possible (a tiny bit of extra CSS is required).

Basically, I wanted to take the laziest path as I could to making dark mode a reality. I also wanted to do this in a way that used modern CSS features while respecting the Global Styles Engine in WordPress.

In this tutorial, I’ll walk through the solution that I came up with and have been using extensively over the past few months. There are some limitations, which I’ll get to, but I believe this is the solution that is the most forward looking.

Setup

For this tutorial, I chose to make a child theme of the Twenty Twenty-Four theme and worked with the parent theme’s default color scheme. You can see the result in these screenshots:

To follow along, make a new child theme named tt4-dark-mode with the following files in it:

  • style.css
  • functions.php
  • theme.json
  • /assets/editor.css

As usual, add your basic file header setup to your style.css file before activating the theme:

/*!
 * Theme Name:        TT4: Dark Mode
 * Description:       Exploring dark mode with Twenty Twenty-Four.
 * Version:           1.0.0
 * Tags:              block-patterns, block-styles, editor-style, full-site-editing, wide-blocks
 * Template:          twentytwentyfour
 * Text Domain:       tt4-dark-mode
 * Tested up to:      6.7
 * Requires at least: 6.6
 */

Using modern CSS features

For this tutorial, you’ll use two CSS features that do not have 100% browser support at the moment, but they are in wide use:

  • color-scheme: CSS property that lets you define what color scheme an element can be safely rendered in. This property has over 95% browser support, so it’s relatively safe to use.
  • light-dark(): CSS color function for defining both light and dark colors. The output color is based on the user’s operating system preferences. It is meant to be used in conjunction with the color-scheme property. It currently has over 86% browser support and can be safely used if supporting modern browsers.

The color-scheme property and light-dark() function are the foundation of the method used in this tutorial. They are both baseline CSS standards in 2024, so the challenge is making them work with WordPress standards.

Enabling the light/dark mode color scheme

For the browser to automatically switch to light or dark mode based on user preferences, you must set the color-scheme property. While this can be set on individual elements, you should target :root for the entire page. color-scheme supports several values, but light dark is the value that tells the browser to determine which scheme to use for light/dark switching.

There is no standard for setting this property via theme.json, but you can use the styles.css field to define custom CSS:

{
	"$schema": "https://schemas.wp.org/trunk/theme.json",
	"version": 3,
	"styles": {
		"css": ":root { color-scheme: light dark; }"
	}
}

One potential issue to watch out for is that the styles.css property can be overwritten in a style variation or child theme’s theme.json by accident. The value from the parent theme.json is not merged. If this could be a problem for your project, try an alternative solution:

Alternative method: Using a stylesheet

I prefer the theme.json method above because it doesn’t require any additional code. But if you want or need to set the color scheme via a stylesheet, you can do so by adding this CSS to your style.css file:

:root {
	color-scheme: light dark;
}

And, of course, you need to ensure that style.css is included in both the front end and editor. Do this by adding this snippet to your functions.php file:

add_action( 'wp_enqueue_scripts', 'tt4_dark_mode_enqueue_assets' );

function tt4_dark_mode_enqueue_assets() {
	wp_enqueue_style( 'tt4-dark-mode', get_stylesheet_uri() );
}

add_action( 'after_setup_theme', 'tt4_dark_mode_theme_setup' );

function tt4_dark_mode_theme_setup() {
	add_editor_style( get_stylesheet_uri() );
}

Designing in two colors using CSS standards

At this point, you’ve got the basic setup out of the way. The hard part of building light and dark color schemes is not in the code itself. It’s in the design—i.e., picking the right colors. That’s more of an art than simply flipping colors around, which won’t look great. And it’s also outside the scope of this tutorial.

I’ll assume you already have your complete light and dark color schemes defined for your own projects. For this tutorial, I’ve already done the work of creating a dark color scheme for Twenty Twenty-Four’s existing default light palette.

What’s important is how to define those colors in theme.json. Do you remember the light-dark() CSS function covered earlier? You’ll use it to define both the light and dark values for each of the colors defined in your theme’s palette.

Here’s an example of what white (#ffffff) and black (#000000) looks like when used with light-dark():

light-dark( #ffffff, #000000 );

This tells the browser to use #ffffff when the user prefers a light scheme and #000000 when they prefer a dark scheme. Any CSS color value is valid here, so the values really depend on your theme’s design.

Now open your theme.json file that you’ve already been working in and add a new settings.color.palette section and define these colors. Your full theme.json file should now look like this:

{
	"$schema": "https://schemas.wp.org/trunk/theme.json",
	"version": 3,
	"settings": {
		"color": {
			"palette": [
				{
					"color": "light-dark(#f9f9f9, #1c1c1c)",
					"name": "Base",
					"slug": "base"
				},
				{
					"color": "light-dark(#ffffff, #111111)",
					"name": "Base / Two",
					"slug": "base-2"
				},
				{
					"color": "light-dark(#111111, #ffffff)",
					"name": "Contrast",
					"slug": "contrast"
				},
				{
					"color": "light-dark(#636363, #888888)",
					"name": "Contrast / Two",
					"slug": "contrast-2"
				},
				{
					"color": "light-dark(#A4A4A4, #3d3d3d)",
					"name": "Contrast / Three",
					"slug": "contrast-3"
				},
				{
					"color": "light-dark(#cfcabe, #60544c)",
					"name": "Accent",
					"slug": "accent"
				},
				{
					"color": "light-dark(#c2a990, #9d7359)",
					"name": "Accent / Two",
					"slug": "accent-2"
				},
				{
					"color": "#d8613c",
					"name": "Accent / Three",
					"slug": "accent-3"
				},
				{
					"color": "light-dark(#b1c5a4, #465a3b)",
					"name": "Accent / Four",
					"slug": "accent-4"
				},
				{
					"color": "light-dark(#b5bdbc, #4d5757)",
					"name": "Accent / Five",
					"slug": "accent-5"
				}
			]
		}
	},
	"styles": {
		"css": ":root { color-scheme: light dark; }"
	}
}

As you can see, almost all of the color definitions have both light and dark hex color codes. I didn’t set one of the colors (accent-three) because it works well in both light and dark mode. That was a bit of a happy accident, but it illustrates that how you define colors is specific to your design.

To test, switch your computer’s display mode between light and dark (there will be different settings based on your operating system).

Small editor fix

The implementation you’ve learned thus far works in both the editor canvas and on the front end. But the dark colors are not reflected in the various color indicators in the editor UI when in dark mode. To fix this, you’ll need a few lines of CSS using the same color-scheme definition you used before.

Create a new file named /assets/editor.css in your theme and add this CSS in it:

.component-color-indicator,
.block-editor-color-gradient-control,
.components-circular-option-picker__option {
	color-scheme: light dark;
}

This tells the various color indicators and controls in the UI to use the preferred light or dark scheme.

Now enqueue this stylesheet in the editor:

add_action( 'enqueue_block_editor_assets', 'tt4_dark_mode_editor_assets' );

function tt4_dark_mode_editor_assets() {
	wp_enqueue_style(
		'tt4-dark-mode-editor',
		get_theme_file_uri( 'assets/editor.css' )
	);
}

Current limitations of this method

One of the major downsides of using light-dark() is that users can overwrite the individual colors in your theme-defined palette via the Styles panel in the Site Editor:

This is problematic because WordPress doesn’t currently have a UI-based method of defining both the light and dark hex codes. When a user overwrites an individual color, their custom color will be used regardless of the mode. 

For distributed themes or sites where the client has access to edit styles, I have yet to find a solution. My initial idea was to disable this functionality, but that is not a supported WordPress feature at this time.

Despite this limitation, this is still a path worth exploring further. color-scheme and light-dark() are already a part of the CSS standard, so it makes sense for WordPress to officially adopt it as part of the UI going forward. 

It’s even possible to make a button to switch between light and dark, but let’s save that for a tutorial on another day. I’d like to see what you come up with in the meantime.

Props to @ndiego and @welcher for feedback and review on this post.

Categories:

6 responses to “Mastering light and dark mode styling in block themes”

  1. Paul Bystrzan Avatar

    Its’ nice, simple and modern, but weak support (in my case Poland, older mac and safari).

    It’s grate as a starting point for next 2-3 years and I need to mention – grate video about this CSS method is on @KevinPowell on YouTube – he covered it mostly, so it’s wotht to see it before making it on production.

    I also saw some grate examples if style.css – not theme.json to fallback for user choice which is always best.

    As always – many ways, but this method probably will be the future. Thank You Justin!

    Best,
    Paul

    1. Justin Tadlock Avatar

      Yep, browser support (depending on your project parameters) is definitely the limiting factor right now. That’s one place where I think dedicated Core support could help even more. It’d be nice if the Style Engine would automatically generate the fallback CSS when light-dark() is in use. Otherwise, devs have to handle this on their own.

      1. Paul Bystrzan Avatar
        Paul Bystrzan

        It would be great! But for now I really like the “Dark Mode Toggle Block” by @RichTabor, which requires you to write your own styles (source: https://github.com/richtabor/dark-mode-toggle-block), and in recent days I’ve been playing even more with the plugin created by @Mosne (source: https://github.com/mosne/mosne-dark-palette), which has a nice user interface, a safe-to-use CSS variable method and an interactive API.

  2. dj.cowan Avatar

    Wonderful! Thank you so much for suggesting this approach. Your post will help us round out and extend similar method we emloyed, although we avoided changing the editor css.

    We took the process in a slightly different direction using the color-mix property *with fallbacks for older browsers.

    We defined a simple theme palette which remains editable in the style engine/editor.

    As we have greater control of the desired output for this project; a set of light-dark custom: { color: … } functions were then defined for each colour in the pallet.
    example:

    {
    "color": "#ee6600",
    "name": "Candy",
    "slug": "candy"
    }

    has a pair of custom color definitions:

    "candy-lower": "primary-lower": "color-mix(in srgb, var(--wp--preset--color--candy), var(--wp--custom--color--contrast) 25%)"
    "candy-lower": "primary-higher": "color-mix(in srgb, var(--wp--preset--color--candy), var(--wp--custom--color--base) 75%)"

    The custom colors generated by the style engie where then applied to blocks and sections as desired.

    .wp-block-kiko-call-out {
    border-color: color-mix(in hsl, var( --wp--custom--color--candy-higher), currentColor 65%));
    background-color: var(
    --wp--custom--color--candy-higher,
    #ee660033
    );

  3. Muhibul Haque Avatar

    This is such a helpful guide—thank you for sharing! I’ve been looking to enable light and dark mode on my site, and I’ll definitely give this a try. Great work!

  4. NICHOLAS AMOL GOMES Avatar
    NICHOLAS AMOL GOMES

    Thank for artickle helpful block

Leave a Reply

Your email address will not be published. Required fields are marked *