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.
Table of Contents
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 thecolor-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.
Leave a Reply