You turn on a dark admin color scheme in WordPress. The toolbar goes dark. The sidebar goes dark. Then you open a post to write, and the actual page you’re typing on is still a bright white rectangle. Here’s why that happens, why it isn’t a bug in the color scheme you picked, and why two other admin surfaces have the exact same problem for two different reasons.
What WordPress’s own dark schemes actually style
WordPress core ships several built-in admin color schemes: Midnight, Ectoplasm, Ocean, and a few others. Every one of them works the same way: a stylesheet targets the admin bar, the sidebar menu, and the general wp-admin chrome around whatever screen you’re looking at. That’s real, useful work, and it’s the entire reason a color scheme exists as a WordPress feature in the first place.
None of them touch the block editor’s actual writing canvas. Open a post with Midnight active and the toolbar above the content area is dark, the sidebar is dark, and the white page you’re typing into sits in the middle of it, untouched. This isn’t an oversight in one scheme. It’s true of every built-in one, because of where the canvas actually lives.
The canvas is a separate document
Since WordPress 5.9, the block editor’s writing canvas renders inside an iframe. Not a visual trick that looks like a separate area: an actual iframe, its own document, loaded with its own stylesheet. This was a deliberate change, done so a block’s appearance in the editor could match how it actually renders on the frontend, isolated from the admin’s own styling.
That isolation cuts both ways. It means the color scheme CSS targeting body.admin-color-midnight on the outer wp-admin page has no way to reach inside that iframe. The selector never matches anything in there, because the iframe’s <body> is a different element entirely, in a different document. A dark color scheme can recolor every pixel of the chrome around the canvas and still have zero effect on the canvas itself, by design, not by mistake.
It also means a plugin that recolors the admin by walking the DOM and adjusting colors element by element runs into the same wall from the other direction. A script running in the outer page’s document has no access to a same-origin iframe’s contents unless it deliberately reaches in and attaches its own logic there too. “Recolor everything on the page” quietly means “recolor everything in this one document,” and the canvas was never part of it.
Why the “normal” fix usually doesn’t work either
WordPress does have a real, documented way for a theme or plugin to add styling inside that iframe: add_editor_style(). It’s the standard approach, and it would be the obvious thing to reach for. There’s a catch that trips up a lot of good-faith attempts at this: add_editor_style() is a no-op unless the site’s active theme has explicitly declared add_theme_support( 'editor-styles' ). Plenty of popular themes never do. On a site running one of those, code built around add_editor_style() can look complete, pass a code review, and then simply do nothing at all when it actually runs, with no error and no warning.
That’s a real trap, not a hypothetical one: it’s the kind of failure that only shows up when someone actually looks at the rendered page, not when reading the code. A plugin that relies on a random site’s theme having opted into a specific support flag is betting on something it has no control over.
What actually reaches the canvas
The reliable path is a different hook entirely: the block_editor_settings_all filter, which lets you append a raw CSS string directly to the block editor’s own settings array before it renders. WordPress injects that CSS into the canvas iframe itself, and this path isn’t gated behind any theme support flag. It works the same way regardless of which theme is active, because it goes through the editor’s own settings system instead of asking the theme to opt in.
The shape of it is short. A plugin reads its own stylesheet and appends it to the settings array’s styles entry:
add_filter( 'block_editor_settings_all', function( $editor_settings ) {
$css = file_get_contents( __DIR__ . '/canvas-dark.css' );
$editor_settings['styles'][] = array( 'css' => $css );
return $editor_settings;
} );That’s the actual mechanism Mawu Dark Mode uses to get its themes into the writing canvas: reading which theme a given user has active, then injecting that theme’s own canvas stylesheet through this filter on every block editor load. The toolbar and sidebar around the canvas need no special handling at all, since those are ordinary wp-admin DOM that a color scheme already reaches. The canvas is the one piece that needed a completely different approach, because it was never part of the same document to begin with.
The Classic Editor has the same problem, solved the same way
Sites still running the Classic Editor plugin hit an almost identical wall. TinyMCE’s writing area also renders inside its own iframe, reachable only through TinyMCE’s own content_css init setting, a plain comma-separated list of stylesheet URLs passed when the editor initializes. add_editor_style() doesn’t help here either, and for the same underlying reason: the content lives in a separate document that the outer page’s styling can’t reach.
The fix looks almost the same as the block editor’s, just through a different door:
add_filter( 'tiny_mce_before_init', function( $init ) {
$url = plugins_url( 'classic-dark.css', __FILE__ );
$init['content_css'] = empty( $init['content_css'] )
? $url
: $init['content_css'] . ',' . $url;
return $init;
} );One extra wrinkle here that the block editor doesn’t have: content_css URLs aren’t run through wp_enqueue_style(), so WordPress never appends its usual cache-busting version query string automatically. A plugin has to add its own version parameter to that URL by hand, or a browser can keep serving a stale cached copy of the stylesheet after an update, with no obvious symptom beyond “I fixed this and it still looks wrong.”
The Customizer’s version of the same story
The Customizer (Appearance → Customize) isn’t an iframe problem at all, which makes it a useful contrast. Its own preview pane is an iframe, showing your actual frontend, and is correctly out of scope for an admin dark mode entirely: that’s the public-facing site, not an admin surface. But the Customizer’s control panel, the actual dark or light UI with all the settings in it, isn’t built on the standard wp-admin page shell. There’s no #wpwrap, no #wpbody, none of the container elements a color scheme or a DOM-walking script would normally expect to find and target.
A recoloring approach built around “find #wpbody and work outward from there” simply has nothing to grab onto on this screen, not because of a document boundary like the canvas, but because the whole layout is different. The practical fix is narrower than the iframe cases: teach whatever’s doing the recoloring that #customize-controls is a second legitimate starting point to walk, alongside #wpbody, rather than assuming there’s only ever one. Mawu’s own per-element engine does exactly this, treating the Customizer’s control panel as a second content root instead of a special case bolted on afterward.
Three surfaces, two different reasons
The block editor canvas and the Classic Editor’s writing area fail for the same reason: a same-origin iframe with its own document, reachable only through the specific injection point each one exposes. The Customizer fails for an unrelated reason: it’s real, walkable wp-admin-adjacent markup, just not rooted where a color scheme or recoloring script assumes it will be. Knowing which kind of problem you’re looking at determines the fix. Treating an iframe boundary like a missing selector wastes time chasing a CSS specificity issue that was never the actual cause, and treating a missing content root like an iframe means reaching for a filter hook that was never going to help either.
If you’ve picked a dark color scheme and wondered why your actual writing area, TinyMCE content, or Customizer controls stayed stubbornly light, now you know why, and it’s not something a “better” color scheme would have fixed on its own. See Mawu Dark Mode for 13 themes that reach all three surfaces, or what Mawu covers in wp-admin for the full technical breakdown.
