Modern WordPress - Yikes!

I loathe what WordPress development has become. If you haven’t kept up with Gutenberg and full-site editing (FSE) you may be surprised at how radically different modern WordPress themes are β€” and not in a good way.

The Flagship Theme

Twenty Twenty-Four” is the flagship WordPress theme for 2024. I expect it to be an exemplary showcase of modern theme development. How it’s meant to be done.

This is the homepage “template” for the flagship theme:

<!-- wp:pattern {"slug":"twentytwentyfour/template-home-business"} /-->

That ain’t the PHP of yore. That’s an HTML file with a single HTML comment that includes… JSON? Yep. That comment references a file found in patterns/. The referenced file is actually PHP. Well it has a .php extension at least.

Let’s analyse it:

 * Title: Business home template
 * Slug: twentytwentyfour/template-home-business
 * Template Types: front-page, home
 * Viewport width: 1400
 * Inserter: no

<!-- wp:template-part {"slug":"header","area":"header","tagName":"header"} /-->

<!-- wp:group {"tagName":"main","style":{"spacing":{"blockGap":"0","margin":{"top":"0"}}},"layout":{"type":"default"}} -->
<main class="wp-block-group" style="margin-top:0">
    <!-- wp:pattern {"slug":"twentytwentyfour/page-home-business"} /-->
<!-- /wp:group -->

<!-- wp:template-part {"slug":"footer","area":"footer","tagName":"footer"} /-->

There is no real PHP here. Just more comments. The file leads with a PHP comment that is some kind of front matter. Following that we have a mixture of HTML and more of those bastardised HTML-JSON comments. This time they’re nested with closing comments like <!-- /wp:group --> β€” interesting.

Let’s consider styling for a minute.

On closer inspection we can see Viewport width: 1400 in the front matter which probably affects visual presentation. There are JSON properties like "margin":{"top":"0"} β€” suspiciously CSS like. We can see an HTML class wp-block-group β€” good; classes are perfect to apply abstracted CSS β€” then we have inline CSS for some reason: style="margin-top:0".

I’ll return to styling later.

If you’re curious about HTML-JSON and why it exists read my article on the WordPress Gutenberg problem. TL;DR it’s a monumental hack. Originally it was generated by React. Now it’s spread to templates and meant to be coded by humans. I think. Code editors aren’t sure how to handle it.

Anyway, in the homepage “template” we can see more wp:pattern and wp:template-part references β€” presumably includes. The latter is similar to the old template function get_template_part. Are “parts” PHP templates then? No, they are not. Files in parts/ are HTML but files in patterns/ are PHP. However, both are largely the same HTML-JSON.

We haven’t gotten to the best part yet.

Like Gutenberg blocks, this HTML-JSON for FSE has a habit of finding its way into the database. I can customise the Header “template” using the full-site editor:

WordPress full site editor

Customised templates are saved to the database:

WordPress database templates

So as a developer, what if you’re asked to change the header template? What happens if you edit parts/header.html? *crickets* β€” that file is not a real template. It’s just a placeholder or reference that may be copied into the database. Once it is, it’s game over.

So how are you supposed to edit templates consistently?

That’s the neat thing, you don’t!

meme: that's the neat thing, you don't.

There is no template. No source of truth. Try an SQL REPLACE on the HTML-JSON, I guess… 🀷

This is the same Gutenberg problem all over again.

Am I going crazy? Is this not the most utterly bizarre chimera of bastardised code you’ve ever seen? I think most developers still imagine WordPress themes as PHP templates. Mercifully, you can still do it that way, but what on earth is this new stuff?


I’ve blogged before about how WordPress broke decades of CSS. My unresolvable GitHub issue is one of many lamenting the abysmal state of affairs. WordPress is often praised for its legacy compatibility. That is not true for CSS. Almost every WP release continues to break CSS specificity in some way.

In the flagship theme code above we’ve already witnessed CSS-in-JSON-in-HTML-comments-in-a-PHP-file. Themes must have a style.css in the root directory.

Theme Name: Twenty Twenty-Four
Theme URI:
Author: the WordPress team

This CSS file is required. God forbid it’s used for actual CSS though. Just more front matter. This file exists for legacy reasons. Perhaps it’s the genesis of WordPress putting stuff in the wrong file format. A trend modern theming takes to the extreme.

Modern themes have a theme.json file; the new hotness. The flagship theme example is too long to include here but it has some gems:

  "styles": {
    "blocks": {
      "core/search": {
        "css": "& .wp-block-search__input{border-radius:.33rem}",
        "typography": {
          "fontSize": "var(--wp--preset--font-size--small)"
        "elements": {
          "button": {
            "border": {
              "radius": {"ref": "styles.elements.button.border.radius"}

Is that partial CSS with the & nesting selector? Who’s the parent? I’m not sure yet. Some styles use custom properties whilst others have hard-coded values. I can’t even find where that "ref" leads to.

Where is the --wp--preset--font-size--small custom property defined? I’m guessing from this array that’s three levels deep:

      "fontSizes": [
          "fluid": false,
          "name": "Small",
          "size": "0.9rem",
          "slug": "small"

Let’s see the generated CSS inlined into a <style> element. I’ve formatted for readability:

.wp-block-search .wp-block-search__label,
.wp-block-search .wp-block-search__input,
.wp-block-search .wp-block-search__button {
  font-size: var(--wp--preset--font-size--small);

So .wp-block-search is the parent I was wondering about. If only CSS could support inheritance, some kind of cascade, you know? Anyway, why is this using BEM-like class names, only to include the parent class? It’s redundant and defeats the point. Maybe it’s done to override the specificity hell of the core block styles.

So far we have seen styles in the HTML-JSON “templates” and more CSS generated by the theme.json monstrosity. Would you like to see more?

Let’s check the theme’s functions.php. Another staple file of WordPress themes.

    'name'         => 'checkmark-list',
    'label'        => __( 'Checkmark', 'twentytwentyfour' ),
      * Styles for the custom checkmark list block style
    'inline_style' => ' {
      list-style-type: "\2713";
    } li {
      padding-inline-start: 1ch;

CSS-in-PHP. That’s a new one. This appears to be a workaround for an open issue. The ul in the selector is unnecessary when using classes. As we’ve seen, unnecessary specificity is a hallmark of WordPress CSS.

More from functions.php:

    'handle' => 'twentytwentyfour-button-style-outline',
    'src'    => get_parent_theme_file_uri( 'assets/css/button-outline.css' ),
    'ver'    => wp_get_theme( get_template() )->get( 'Version' ),
    'path'   => get_parent_theme_file_path( 'assets/css/button-outline.css' ),

Is that loading an actual stylesheet? Yes it is!
  > .wp-block-button__link:not(.has-text-color, .has-background):hover {
  background-color: var(--wp--preset--color--contrast-2, var(--wp--preset--color--contrast, transparent));
  color: var(--wp--preset--color--base);
  border-color: var(--wp--preset--color--contrast-2, var(--wp--preset--color--contrast, currentColor));

This is the only .css stylesheet for the entire theme. Why this? Why now? I have no answer. There’s not much more to say about the gnarly selectors. They’re a product of WordPress’ non-existent CSS strategy.

So to recap, the flagship WordPress theme has:

  • CSS in front matter
  • CSS in PHP
  • CSS in JSON
  • CSS in HTML <style> elements
  • CSS in HTML style attributes
  • CSS in external stylesheets

No CSS-in-JS for the complete set sadly.

Some of these styles are hard-coded, others are generated. Some styles exist in files, others in the database. The combined CSS is a tangled mess that all gets inlined into the rendered page. In what order? Who knows. And then bugs crop up so you can never be quite sure.

My advice would be to once again ignore all this new stuff and write a good old fashion separation-of-concerns stylesheet. WordPress will fight you, and the core styles will make you cry, but at least you have some hope of coding maintainable CSS.


I just find this whole thing perplexing. How did WordPress get to this state? Modern themes are this weird code-within-code unmaintainable inception that looks like AI LLM generate garbage. I honestly question just who is developing themes like this? Is anyone out there? Seriously, @ me on Mastodon if you know. From what I see everyone is sticking to PHP templates. I cannot imagine a young developer ever wanting to learn this new nonsense.

Unfortunately for me WordPress development has been my bread and butter. That is changing. I’m doing a lot less of it lately. Frankly, I hope that trend continues!

Something Good

WordPress Studio is a new official app for local development. A decade ago I was using MAMP Pro for local PHP & MySQL. In recent years I’ve been using Docker.

WP Studio is an Electron app that manages the web stack and bootstraps new projects. My initial impressions are positive. It creates an SQLite database right in the project directory. A default .gitignore file would be helpful.

You can spin up and get coding on a WordPress site in seconds. Good stuff. Give it a try and poke around the flagship theme 😬

Update for 8th May 2024

I guess I spoke too soon praising WordPress Studio!

Yesterday I started a new project on WP 6.5.2. Today I woke up to the WordPress Admin prompting me with the 6.5.3 update. I tried to update. The database seems to have corrupted itself. Beyond repair? I’ve opened a GitHub issue.

Luckily I had nothing set up so I can start a new site project on 6.5.3. Do I roll the dice?

I’m giving this app one last chance before I bin it for good.

Update 2: fool me twice, shame on me. It’s binned. I’m back to Docker.

Update for 13th May 2024

I’ve written a small follow-up to discuss feedback and corrections.

Buy me a coffee! Support me on Ko-fi