# Theme.json setup in Sage 11

**URL:** https://discourse.roots.io/t/theme-json-setup-in-sage-11/29514
**Category:** sage
**Tags:** sage11
**Created:** 2025-04-17T18:24:03Z
**Posts:** 23

## Post 1 by @eavonius — 2025-04-17T18:24:03Z

Sage 11 suggests this in the vite config:

```
wordpressThemeJson({
  disableTailwindColors: false,
  disableTailwindFonts: false,
  disableTailwindFontSizes: false,
}),
```

I had a bunch of config in bud in Sage 10 that got generated into the theme.json. I don’t see a place for that here, but I did notice there’s a `tailwindConfig` setting on `ThemeJsonConfig` (the type of options to this function) that can point to a tailwind.config.js file. I also see `baseThemePath` that defaults to `./theme.json` according to the comments in that file. As well as a `outputPath` that defaults to `assets/theme.json` according to the comments.

Is this currently working? I don’t see it creating root variables for the theme settings I had before, just the generic defaults from plain wordpress. And does that make sense that the generated file goes into the assets directory, and not right into the root by default where Wordpress would look for it?

---

## Post 2 by @ben — 2025-04-17T20:58:48Z

> [@eavonius](#):
>
> Is this currently working?

Yes

> [@eavonius](#):
>
> I don’t see it creating root variables for the theme settings I had before, just the generic defaults from plain wordpress

[Post your code](https://discourse.roots.io/t/how-to-best-ask-questions-on-this-forum/24582#post-your-code-error-messages-and-logs-4)

> [@eavonius](#):
>
> And does that make sense that the generated file goes into the assets directory, and not right into the root by default where Wordpress would look for it?

> <https://github.com/roots/sage/blob/5ffb5636b2eae99afd6522eaba7e7809d8b939f5/app/setup.php#L49-L58>

---

## Post 3 by @eavonius — 2025-04-18T13:58:50Z

Thank you, this helped me get closer.

I’ve not written a vite plugin, so I’m not an expert on this. I simply stopped the dev server, added console.log statements to functions in the plugin, restarted the dev server, and refreshed the page to try and determine what’s going on.

When I run `vite dev`, I never see `generateBundle` get called, which it looks like is where the merging of the base theme.json and generated theme styles happen.

When I do a full vite build, I DO see the merged theme.json placed in the right path with the correct content.

Here’s a link to the function in the roots vite plugin that I’m referring to:

> <https://github.com/roots/vite-plugin/blob/5a8143206021be050690ef4e1f50ba93bb6e9984/src/index.ts#L844>

---

## Post 4 by @ben — 2025-04-18T14:22:53Z

Again, post your code.

---

## Post 5 by @ben — 2025-04-18T14:22:56Z



---

## Post 6 by @eavonius — 2025-04-18T21:09:34Z

Which files would be most helpful to help you determine if this is an issue?

---

## Post 7 by @ben — 2025-04-20T15:51:34Z

Unless I’m mistaken, we’re talking about your generated theme.json file being incorrect. Share your theme.json file from the theme root that you’ve made changes to. Looking for what steps are needed to make a minimal reproduction of what you’re describing.

Otherwise there might be confusion as to how you’re supposed to be working with theme.json as of Sage 11?

---

## Post 8 by @eavonius — 2025-04-21T22:31:34Z

Thanks Ben. It’s very possible I could be misunderstanding the intention of how to work with theme.json files in sage 11.

From the jsdoc comments, it seemed like the idea is I can have a theme.json at the root of my sage theme, and the plugin will merge it with any styles defined in the `@theme` layer of my app.css. And that I can optionally also specify some ways that labels are mapped to styles in the @theme layer.

What I was trying to do, was use my existing theme.json from sage 10 (which was generated by bud) and have it be merged with theme styles I used to define in tailwind.config.js, but are now in the `@theme` layer in app.css.

I actually reuse styles in the @theme layer in both app.css and editor.css by importing them from another shared css file. That doesn’t seem to be currently supported (only theme styles directly in app.css are supported) but that’s a seprate issue.

After some extensive testing it seems to me that the Wordpress `theme_file_path` hook in `setup.php` is looking for theme.json at `build/assets/theme.json`, but that file only gets written when you do a `vite build`.

This could be a completely different direction than you intended, but to at least get myself up and running, I ended up writing a PR that watches for changes to theme.json or any of your css files and runs the same code you were using to create the “merged” file in `generateBundle`, but now also when running `vite dev`. It’s kept me going for now, but I have no idea how the approach I took fits with your plans.

You can check that out here: [feat: generate theme.json during dev mode using in-memory CSS by eavonius · Pull Request #15 · roots/vite-plugin · GitHub](https://github.com/roots/vite-plugin/pull/15)

Below is the contents of my theme.json at the root of my theme.

```
{
  "$schema": "https://schemas.wp.org/trunk/theme.json",
  "version": 3,
  "settings": {
    "appearanceTools": true,
    "layout": {
      "contentSize": "1280px",
      "wideSize": "1512px"
    },
    "color": {
      "custom": false,
      "link": true,
      "customDuotone": false,
      "customGradient": false,
      "defaultDuotone": false,
      "defaultGradients": false,
      "defaultPalette": false,
      "duotone": [],
      "palette": [
        {
          "slug": "primary",
          "color": "#29d48d",
          "name": "Primary"
        },
        {
          "slug": "primary-border",
          "color": "#2CE698",
          "name": "Primary border"
        },
        {
          "slug": "primary-hover",
          "color": "#2CE698",
          "name": "Primary hover"
        },
        {
          "slug": "primary-hover-border",
          "color": "#2CFF98",
          "name": "Primary hover border"
        },
        {
          "slug": "secondary",
          "color": "#B83259",
          "name": "Secondary"
        },
        {
          "slug": "secondary-border",
          "color": "#ca3f66",
          "name": "Secondary border"
        },
        {
          "slug": "secondary-hover",
          "color": "#ca3f66",
          "name": "Secondary hover"
        },
        {
          "slug": "secondary-hover-border",
          "color": "#d35f80",
          "name": "Secondary hover border"
        },
        {
          "slug": "tertiary",
          "color": "#219FC4",
          "name": "Tertiary"
        },
        {
          "slug": "tertiary-border",
          "color": "#2eb3dc",
          "name": "Tertiary border"
        },
        {
          "slug": "tertiary-hover",
          "color": "#2eb3dc",
          "name": "Tertiary hover"
        },
        {
          "slug": "tertiary-hover-border",
          "color": "#51c0e1",
          "name": "Tertiary hover border"
        },
        {
          "slug": "muted",
          "color": "#555",
          "name": "Muted"
        },
        {
          "slug": "dark",
          "color": "#333",
          "name": "Dark"
        },
        {
          "slug": "dark-link",
          "color": "#29c1ec",
          "name": "Link on dark"
        },
        {
          "slug": "disabled",
          "color": "#888",
          "name": "Disabled"
        },
        {
          "slug": "disabled-text",
          "color": "#444",
          "name": "Disabled text"
        },
        {
          "slug": "black",
          "color": "#111",
          "name": "Black"
        },
        {
          "slug": "Grey",
          "color": "#ddd",
          "name": "grey"
        },
        {
          "slug": "white",
          "color": "#fff",
          "name": "White"
        },
        {
          "slug": "off-white",
          "color": "#eee",
          "name": "Off white"
        },
        {
          "slug": "gold",
          "color": "#d6aa14",
          "name": "Gold"
        },
        {
          "slug": "primary-dark",
          "color": "#1f8e6b",
          "name": "Primary Dark"
        }
      ]
    },
    "custom": {
      "spacing": {},
      "typography": {
        "font-size": {},
        "line-height": {}
      }
    },
    "spacing": {
      "blockGap": true,
      "margin": true,
      "padding": true,
      "units": ["px", "%", "em", "rem", "vw", "vh"]
    },
    "typography": {
      "dropCap": true,
      "lineHeight": true,
      "customFontSize": false,
      "fontSizes": [
        {
          "slug": "tiny",
          "size": "clamp(0.56rem, calc(0.38rem + 0.39vw), 0.69rem)",
          "name": "Tiny"
        },
        {
          "slug": "small",
          "size": "clamp(0.8rem, calc(0.56rem + 0.39vw), 0.88rem)",
          "name": "Small"
        },
        {
          "slug": "normal",
          "size": "clamp(1.05rem, calc(0.50rem + 0.78vw), 1.18rem)",
          "name": "Normal"
        },
        {
          "slug": "large",
          "size": "clamp(1.1rem, calc(0.63rem + 0.78vw), 1.25rem)",
          "name": "Large"
        },
        {
          "slug": "x-large",
          "size": "clamp(1.1rem, calc(0.75rem + 0.78vw), 1.38rem)",
          "name": "Extra Large"
        },
        {
          "slug": "xx-large",
          "size": "clamp(1.3rem, calc(1.03rem + 0.98vw), 1.81rem)",
          "name": "Extra Extra Large"
        },
        {
          "slug": "heading-one",
          "size": "clamp(2em, calc(1.59rem + 1.76vw), 3.00rem)",
          "name": "Heading One"
        },
        {
          "slug": "heading-two",
          "size": "clamp(1.85rem, calc(1.53rem + 1.37vw), 2.63rem)",
          "name": "Heading Two"
        },
        {
          "slug": "heading-three",
          "size": "clamp(1.6rem, calc(1.25rem + 1.17vw), 2.19rem)",
          "name": "Heading Three"
        },
        {
          "slug": "heading-four",
          "size": "clamp(1.3rem, calc(1.03rem + 0.98vw), 1.81rem)",
          "name": "Heading Four"
        },
        {
          "slug": "heading-five",
          "size": "clamp(1.1rem, calc(0.75rem + 0.78vw), 1.38rem)",
          "name": "Heading Five"
        },
        {
          "slug": "heading-six",
          "size": "clamp(1.1rem, calc(0.63rem + 0.78vw), 1.25rem)",
          "name": "Heading Six"
        },
        {
          "slug": "subtitle",
          "size": "clamp(1.1rem, calc(0.75rem + 0.78vw), 1.38rem)",
          "name": "Subtitle"
        }
      ],
      "fontFamilies": [
        {
          "fontFamily": "\"Inter\", sans-serif",
          "slug": "open-sans",
          "name": "Open Sans"
        },
        {
          "fontFamily": "\"Jost\", sans-serif",
          "slug": "jost",
          "name": "Jost"
        }
      ]
    }
  },
  "styles": {}
}
```

---

## Post 9 by @eavonius — 2025-04-23T19:53:45Z

@ben so a complication I ran into, is that as soon as build/assets/theme.json exists, it appears that when this code runs in the `admin_head` hook in `setup.php` for the sage theme:

```
$dependencies = json_decode(Vite::content('editor.deps.json'));
```

It finds the build directory there and thinks this is a prod build and errors out since it can’t find the editor deps.

I didn’t realize this was a problem until I tried to hop into the admin side of the site once the “built” theme.json exists. So I’m not sure what to do next.

---

## Post 10 by @eavonius — 2025-04-24T12:58:06Z

I was able to get this to still work if I add a check for `WP_ENV` being set to anything other than `development` (I have a staging and production environment for my site):

```
add_filter('admin_head', function () {
    if (! get_current_screen()?->is_block_editor()) {
        return;
    }

    if (defined("WP_ENV") && WP_ENV !== 'development') {
        $dependencies = json_decode(Vite::content('editor.deps.json'));

        foreach ($dependencies as $dependency) {
            if (! wp_script_is($dependency)) {
                wp_enqueue_script($dependency);
            }
        }
    }

    echo Vite::withEntryPoints([
        'resources/js/editor.js',
    ])->toHtml();
});
```

---

## Post 11 by @ben — 2025-04-24T13:15:30Z



---

## Post 12 by @Log1x — 2025-04-25T16:23:50Z

> I was able to get this to still work if I add a check for `WP_ENV` being set to anything other than `development` (I have a staging and production environment for my site):

I’m confused on when this would ever be necessary? In the current configuration (outside of your PR)- you’re expected to have done an initial `npm run build` before going into development mode, but otherwise everything should work as expected. I’m not against `theme.json` being watched/built in dev mode, I just hadn’t thought about it. I just want to make sure there’s not another issue I’m missing here?

---

## Post 13 by @eavonius — 2025-04-25T18:48:41Z

Yes, that’s the only issue. In bud we had the theme.json generated hot I believe, so I just expected that was the same here.

I have several custom gutenberg blocks and content I work on at the same time as blade code, so I have just grown accustomed to being able to update the theme to make stuff available to the content editor without having to stop the server.

It’s not a deal-breaker, I just didn’t see anywhere that a build was required first before doing development. That could have been something I just overlooked, or I may be the only one working with developing my theme and editing content at the same time.

---

## Post 14 by @Yuriy_Vasilyev — 2025-05-10T11:22:28Z

@eavonius , check the `theme.json` in the root theme folder. Now you can edit it, and the auto-generated version will be in `dist/build`.

---

## Post 15 by @eavonius — 2025-05-10T17:42:31Z

Shouldn’t it be in `public/build/assets` since that’s where the `theme_file_path` hook points in `app/setup.php`?

---

## Post 16 by @Log1x — 2025-05-10T17:48:03Z

the base `theme.json` gets merged into the built one from the Vite plugin. you can modify it freely.

---

## Post 17 by @eavonius — 2025-05-10T18:31:24Z

Right. The issue is the built one wasn’t showing up in `public/build/assets` where the `theme_file_path` expects it unless you do a `vite build`, which then publishes everything.

I submitted a PR that adds hot generation so you don’t have to do that.

But I saw the response from @Yuriy_Vasilyev saying it is being auto-generated, but in a path that doesn’t make sense to me.

I guess I’m confused.

---

## Post 18 by @eavonius — 2025-05-11T16:23:03Z

I saw that the PR progressed further by running tests that failed. While working on getting these tests to pass, I feel working on this plugin code has become hard to navigate with a 1300 line file.

I would like to suggest (I could submit this as a PR) that we break the code up in this plugin like this:

```
src/
└── plugin/
    ├── index.ts
    ├── wordpress-plugin.ts
    └── theme-json/
        ├── index.ts
        ├── tailwind.ts
        ├── theme-extractor.ts
        ├── theme-json.ts
        └── types.ts
```

With these purposes:

### `src/plugin/index.ts`

- Entry point, exports both `wordpressPlugin` and `wordpressThemeJson`
- Just wiring and parameter passthrough.

* * *

### `src/plugin/wordpress-plugin.ts`

- The classic WP transform:
  - Converts `@wordpress/*` imports
  - Handles dependency tracking and `editor.deps.json` emission
  - Optional HMR for editor iframe

* * *

### `src/plugin/theme-json/index.ts`

- Defines the `wordpressThemeJson()` plugin factory
- Handles Vite plugin lifecycle methods: `configResolved` , `transform` , `generateBundle` , `configureServer`

This would import from the following:

* * *

### `src/plugin/theme-json/theme-extractor.ts`

- Handles `@theme` parsing :
  - `extractThemeContent()`
  - `extractVariables()`
  - Combines content from original and emitted CSS
  - Maybe a `combineCssContent()` util to merge `transform()` + `generateBundle()` inputs

* * *

### `src/plugin/theme-json/tailwind.ts`

- Handles:
  - `loadTailwindConfig()`
  - `mergeThemeWithExtend()`
  - `flattenColors()`
  - `processFontFamilies()`
  - `processFontSizes()`

* * *

### `src/plugin/theme-json/theme-json.ts`

- Logic to generate the final theme.json object:
  - `generateThemeJson()`
  - `writeThemeJsonToDisk()`
  - Deduplication, sorting, merging, filtering

* * *

### `src/plugin/theme-json/types.ts`

- Interfaces:
  - `ThemeJson` , `ColorPalette` , `FontFamily` , `FontSize`
  - Plugin options

Thoughts?

---

## Post 19 by @ben — 2025-05-11T16:52:25Z

I’ve been very confused about this topic since it was posted :smiley:

> [@eavonius](#):
>
> In bud we had the theme.json generated hot I believe, so I just expected that was the same here.

**Nope, this was not a thing.** Confirmed with these steps:

1. Fresh installation Sage v10.8.2 with Acorn v4.3.0
2. Run `yarn dev`
3. Login to wp-admin at `http://localhost:3000/wp/wp-login.php`
4. Create/edit a post
5. Look at the colors, font sizes, and font families available
6. Modify the Tailwind config to change the colors
7. Refresh the editor
8. **Nothing has changed**

If you run the regular build then refresh, expected color palette is shown

> [@eavonius](#):
>
> But I saw the response from @Yuriy_Vasilyev saying it is being auto-generated, but in a path that doesn’t make sense to me.
> 
> I guess I’m confused.

What exactly is confusing about the path?

> [@eavonius](#):
>
> I would like to suggest (I could submit this as a PR) that we break the code up in this plugin like this:

If we’re going to try to get a “hot” theme.json file working then sure, we could consider re-organizing the code since your PR is currently adding 1/3rd of the existing LoC to the one file

---

## Post 20 by @eavonius — 2025-05-11T17:19:47Z

Ah I see you are right. Before sage 11, I was checking the generated theme.json into my repo at the root of my theme source, so it probably wasn’t regenerated each time!

The question stands though, does it make sense to require someone to do a full build and have the `public/build/assets` dir populated with everything in a production build, just to have their expected theme.json available in development?

As to my comment to @Yuriy_Vasilyev I did my best to explain it above. The hook in sage 11 is looking in a different path than he mentioned. That’s what’s confusing.

Hope that helps.

---

## Post 21 by @ben — 2025-05-11T17:29:46Z

> [@eavonius](#):
>
> The question stands though, does it make sense to require someone to do a full build and have the `public/build/assets` dir populated with everything in a production build, just to have their expected theme.json available in development?

It would of course be great and ideal if we could add support for the editor reflecting the expected `theme.json` file when the Vite dev server is running.

> [@eavonius](#):
>
> The hook in sage 11 is looking in a different path than he mentioned. That’s what’s confusing.

Ah, they said `dist` when they meant `public`.

I think we’re all trying to say the same thing here. The only difference between Sage 10 and Sage 11’s `theme.json` setup is that you modify the root `theme.json` file now instead of your bud.js config.

---

## Post 22 by @franticnomad — 2025-07-02T19:18:52Z

" It would of course be great and ideal if we could add support for the editor reflecting the expected `theme.json` file when the Vite dev server is running."

Is this feature in the works? I’m new to this theme and found this thread thinking something was broken as _npm run dev_ does reload theme.json, but apparently doesn’t rebuilt it currently, correct? i.e. npm run build is required?

---

## Post 23 by @ben — 2025-07-03T13:33:17Z

We are not experts at building Vite plugins and would love any help (and would be willing to pay for it) from someone who would be able to implement support for writing the `theme.json` file, and updating it when changes are made, when the Vite dev server is running
