The block editor doesn’t match your front end, and it’s not your stylesheet

Hand-drawn whiteboard diagram: in the editor canvas iframe, core's editor-only content.css outweighs the theme stylesheet, splitting a word mid-line; on the front end only the theme stylesheet exists and renders as designed. The fix: an override with the same selectors loaded after core, so the last rule wins.

A word in my block theme’s editor canvas was splitting in half: dogfo on one line, oding on the next. The front end rendered the same paragraph perfectly. Same content, same theme, same CSS.

If you search this class of problem, every answer tells you to load your styles into the editor: add_theme_support( 'editor-styles' ), add_editor_style( 'style.css' ), done. Reasonable advice. It was also already true in my case: my stylesheet was in the canvas, visible in devtools, selectors dutifully rewritten under .editor-styles-wrapper, exactly as the Theme Handbook describes. The handbook’s story ends there. Mine didn’t, because loading your CSS is the necessary half of parity. The other half is what else got loaded next to it.

The editor canvas is an iframe, and WordPress ships its own stylesheet into it: wp-includes/css/dist/block-editor/content.css, which has opinions about every block. It applies them through wrapper classes that exist only in the editor, like this one (I’m reading WordPress 7.0.2):

.block-editor-block-list__layout .block-editor-block-list__block {
	overflow-wrap: break-word;
}

That selector weighs in at specificity 0,2,0: no IDs, two classes, no bare elements. Those numbers matter, but specificity is only one round of the fight. When several rules claim the same property, CSS runs a tournament:

  1. Importance and origin. Browser defaults lose to your stylesheet, and !important overrules the mortal realm.
  2. Specificity. The selector-weighing round, where most everyday fights end. More classes wins.
  3. Source order. Still tied? Whoever spoke last wins.

(There are a couple of exotic rounds between two and three, cascade layers and scoping, that most stylesheets never enter.) “More classes wins” is the working shorthand because fights rarely get past round two. But keep round three in your pocket. It decides the ending of this post.

Now look at what a modern theme’s rules weigh. Block themes lean on :where(), a wrapper that makes everything inside it count as zero: a selector that walks into the tournament and forfeits its round on purpose. That sounds like a design flaw until you learn whose idea it was. Core wraps its own global-styles output in :where() too, and its source carries a comment saying outright that the wrapper is there to lower the specificity, so that user styles and per-block tweaks can always outrank the theme’s defaults. Losing gracefully to the user is the whole idea. My theme’s :where(p, li, ...) { overflow-wrap: normal; } follows the same philosophy. Weight zero.

On the front end, the editor-only wrapper classes don’t exist, so the zero-weight rule is the only voice and everything renders as designed. In the canvas, core’s 0,2,0 beats zero every time. The editor diverges not because your CSS is missing but because a second stylesheet with heavier selectors is speaking over it, only there. (The trigger token was self-inflicted, for the record: an earlier bad find-and-replace in my build had joined two words with a non-breaking space, and that extra-long token is what gave break-word something to break. If a normal word is splitting, check for invisible characters before blaming the CSS.)

And yes, I deleted the space. That cured the paragraph, not the disagreement. The front end still says overflow-wrap: normal and the canvas still says break-word, and two surfaces that disagree about a property will disagree again the first time qualifying content shows up: a long URL, a code identifier, a German compound noun with ambitions. The rogue space is how the divergence got noticed. It was never what was wrong. So fix the class, not the instance.

The fix pattern is a targeted editor-only override, and here’s round three from earlier: it uses core’s own selectors, so specificity ends in a dead heat and the decision goes to source order. Our sheet loads after core’s. Whoever speaks last wins:

/* assets/admin/editor-canvas.css - canvas only, wins by source order */
.block-editor-block-list__layout .block-editor-block-list__block,
.block-editor-rich-text__editable {
	overflow-wrap: normal;
}

Two traps inside that small fix:

  1. The delivery hook. WordPress has two enqueue hooks with nearly identical names, and the names don’t tell you the one thing that matters: which side of the iframe they land on. enqueue_block_assets loads your file inside the canvas, where your content lives. enqueue_block_editor_assets loads it into the editor’s outer chrome, the toolbars and panels, where it can never reach a block. You want the first, guarded with is_admin() so it stays off the front end.
  2. The actual element. Override the element that holds the text, which is not always the block wrapper. In list items and headings it’s a child .block-editor-rich-text__editable, and core sets the property there too. Read the failing element’s parent chain with getComputedStyle instead of assuming.

The good news: this problem doesn’t sprawl. Core’s heavy editor-only rules live in a single file, they only change when WordPress ships a major release, and the handful that affect how text renders is small. A short, slow-moving list is something a build check can guard. Mine has two jobs: it knows every core rule I’ve had to override, and it fails the build if any override goes missing. When a new WordPress version lands, I rerun it against the new content.css to see whether core added anything. So the editor matches the front end because the build proves it, not because someone eyeballed two screenshots side by side. I tried the screenshot-comparison approach once. Once.

So when the editor and the front end disagree and your stylesheet is provably in the canvas: stop debugging your CSS. Open devtools inside the iframe, find the winning rule on the actual element, and check whether its classes start with block-editor-. If they do, you’ve met the second stylesheet.