You have built a Tailwind theme you like. Maybe it came out of a Tailwind theme generator, maybe you tuned every shade by hand in Figma. The colours feel right, the spacing has rhythm, the type scale reads well. Then the site moves into WordPress, and the block editor shows its own colour picker with its own swatches, its own font sizes and its own spacing steps. Editors pick a blue that is almost, but not quite, your blue.

This guide is for the designer who owns that palette. It walks through turning a Tailwind theme into theme.json so the editor and the front end use one set of colours, spacing and fonts. Not two palettes kept in sync by hand. One. We will start from design decisions, not code, and only reach for code once the decisions are made.

Why a Tailwind theme and a block theme fight

Both systems want to be the source of truth for the same things.

Design decisionTailwind keeps it inWordPress keeps it in
Colours@theme variables like --color-brand-500settings.color.palette in theme.json
Font families--font-* variablessettings.typography.fontFamilies
Font sizes--text-* variablessettings.typography.fontSizes
Spacing--spacing scalesettings.spacing.spacingSizes

Tailwind turns its variables into utility classes. WordPress turns theme.json presets into editor controls (the swatches, the font size picker, the spacing slider) and into CSS custom properties named like --wp--preset--color--brand. If you define colours in both places, you now have two palettes that drift the moment someone edits one of them.

The fix is simple to say: pick one place to own the values, and have the other read from it. For a block theme, theme.json should own them, because that is what editors see and touch. Tailwind then reads the CSS variables WordPress generates. The rest of this guide is how to get there without losing what makes your Tailwind theme good.

Step 1: decide what the palette is for

A Tailwind theme generator hands you a lot: usually eleven shades per colour, from 50 to 950, for every hue you give it. That is great for building interfaces in code. It is far too much for an editor’s colour picker. Nobody writing a blog post should be choosing between brand-600 and brand-700.

So before any code, sort your colours into two groups.

  • Roles editors should pick. These go in the theme.json palette and show up as swatches: the brand colour, an accent, the main text colour, a muted text colour, the page background, a surface for cards, and maybe a success and a warning colour. Eight to twelve is plenty.
  • Shades the design system uses. Hover states, borders, subtle tints behind callouts. Designers and theme code need them; editors usually do not. These can stay out of the swatches and still be available to Tailwind.

Name the roles by what they do, not by what they look like. “Brand”, “Ink” and “Surface” survive a rebrand. “Blue” does not.

Step 2: generate the scale, honestly

A Tailwind theme generator is a good way to turn one brand colour into a usable scale. A few worth knowing:

  • UI Colors takes one colour and builds a 50 to 950 scale you can tweak and export as Tailwind CSS.
  • tints.dev does the same with more control over hue and saturation per step, and is handy when a generated scale looks muddy in the middle.
  • Tailwind’s own default palette is a well-tuned reference. In v4 it is defined in OKLCH, which keeps perceived lightness more even across hues than older hex palettes.

Two honest caveats about generators. First, they are guessing. A scale that looks smooth in a grid can still have a 600 that fails contrast on white, or a 100 that is too close to the background to see. Second, they know nothing about your brand. If your brand blue is a specific value from a style guide, pin it at the step you use most (often 500 or 600) and adjust the others around it, rather than letting the generator move it.

Check contrast before you go further. Text colours need at least 4.5:1 against the background they sit on for normal text, and 3:1 for large text, under WCAG AA. Check your “Ink” and “Muted” roles against “Base” and “Surface”, and check text on your brand colour if editors will put text on brand-coloured buttons or sections.

Step 3: write the token table

This is the document that ends arguments. One row per token, with the role, the value and where it is used. It takes twenty minutes and saves weeks.

Role (slug)ValueEditor swatch?Used for
brand#3858e9YesButtons, links, key highlights
brand-soft#eef1fdYesCallout backgrounds
accent#f59e0bYesBadges, small emphasis
ink#101828YesBody text, headings
muted#475467YesCaptions, meta text
base#ffffffYesPage background
surface#f6f8fcYesCards, panels
border#dbe2eeNoDividers, card borders
brand-700#2a3fb0NoButton hover

Do the same, more briefly, for fonts (heading and body), font sizes (five to seven steps is enough for most sites) and spacing (six to eight steps). Our post on design tokens in theme.json goes deeper on structuring these.

The contrast check for this example palette

Here are the real ratios for the pairs that matter in the table above, worked out with the WCAG formula:

TextOnRatioNormal text (4.5:1)
Ink #101828Base #ffffff17.75:1Pass
Muted #475467Base #ffffff7.69:1Pass
Muted #475467Surface #f6f8fc7.23:1Pass
WhiteBrand #3858e95.61:1Pass
Brand #3858e9Brand soft #eef1fd4.98:1Pass
Ink #101828Accent #f59e0b8.26:1Pass

Notice the last row. White text on that amber accent would fail, so the token table should say “dark text on accent”, and your button styles should enforce it. Writing that down now stops an editor from discovering it on a live page.

Step 4: put the roles into theme.json

Now the decisions become code. The colour roles that editors should pick go into the palette. Turn off WordPress’s default palette and custom colour picker if you want editors to stay on brand:

{
  "version": 3,
  "settings": {
    "color": {
      "defaultPalette": false,
      "custom": false,
      "palette": [
        { "slug": "brand", "color": "#3858e9", "name": "Brand" },
        { "slug": "brand-soft", "color": "#eef1fd", "name": "Brand soft" },
        { "slug": "accent", "color": "#f59e0b", "name": "Accent" },
        { "slug": "ink", "color": "#101828", "name": "Ink" },
        { "slug": "muted", "color": "#475467", "name": "Muted" },
        { "slug": "base", "color": "#ffffff", "name": "Base" },
        { "slug": "surface", "color": "#f6f8fc", "name": "Surface" }
      ]
    }
  }
}

Fonts and sizes go in the same file. Fluid font sizes let a heading scale smoothly between phone and desktop without extra breakpoints:

"typography": {
  "fluid": true,
  "fontFamilies": [
    { "slug": "heading", "name": "Heading", "fontFamily": "\"Inter\", sans-serif" },
    { "slug": "body", "name": "Body", "fontFamily": "\"Inter\", sans-serif" }
  ],
  "fontSizes": [
    { "slug": "small", "name": "Small", "size": "0.875rem" },
    { "slug": "medium", "name": "Medium", "size": "1rem" },
    { "slug": "large", "name": "Large", "size": "1.25rem", "fluid": { "min": "1.125rem", "max": "1.25rem" } },
    { "slug": "x-large", "name": "Extra large", "size": "2rem", "fluid": { "min": "1.5rem", "max": "2rem" } }
  ]
}

WordPress’s colour settings and typography settings documentation cover every option. Once this file is in place, the Site Editor shows your swatches and sizes, and WordPress outputs each one as a CSS variable such as --wp--preset--color--brand and --wp--preset--font-size--large.

Step 5: let Tailwind read from theme.json

Here is where the two palettes become one. Instead of writing hex values in Tailwind’s @theme, point each Tailwind variable at the WordPress variable. If you want only your own colours in Tailwind, clear the defaults first with the --color-*: initial syntax from Tailwind’s theme variables docs:

@theme {
  --color-*: initial;
  --color-brand: var(--wp--preset--color--brand);
  --color-brand-soft: var(--wp--preset--color--brand-soft);
  --color-accent: var(--wp--preset--color--accent);
  --color-ink: var(--wp--preset--color--ink);
  --color-muted: var(--wp--preset--color--muted);
  --color-base: var(--wp--preset--color--base);
  --color-surface: var(--wp--preset--color--surface);
  --color-border: #dbe2ee;
  --color-brand-700: #2a3fb0;
}

Now bg-brand in a pattern and the “Brand” swatch in the editor are literally the same value. Change the brand colour in Styles in the Site Editor, and every Tailwind utility that uses it follows. The design-system-only shades (border, brand-700) live in Tailwind alone, because editors never pick them.

For the developer side of this bridge, including build setup and edge cases, our sister site has a detailed guide to bridging Tailwind v4 and theme.json in block themes. If you have not set up a Tailwind build in WordPress yet, start with our guide to the Tailwind CSS CDN and when to add a build step.

Step 6: test it the way editors will

  1. Open the Site Editor and check the colour picker shows only your roles, with clear names.
  2. Change “Brand” to something obviously wrong, like bright green. Every button, link and Tailwind-styled section should turn green, in the editor and on the front end. Change it back.
  3. Add a heading at each font size on a phone-width preview and confirm the fluid sizes scale sensibly.
  4. Put muted text on the surface colour and check it is still readable.
  5. Ask an editor to build a simple page using only the swatches. If they reach for a colour that is not there, either add the role or explain why it is not one.

Step 2 is the important one. If anything stays blue, it has a hard-coded colour somewhere, and that is the next drift waiting to happen.

Typography: from Tailwind’s text scale to WordPress font sizes

A Tailwind theme usually defines a type scale with names like text-sm, text-lg and text-4xl. WordPress’s font size picker shows its own named sizes. The same rule applies as for colours: decide the sizes editors should choose, put them in theme.json, and have Tailwind read them.

@theme {
  --font-heading: var(--wp--preset--font-family--heading);
  --font-body: var(--wp--preset--font-family--body);
  --text-small: var(--wp--preset--font-size--small);
  --text-medium: var(--wp--preset--font-size--medium);
  --text-large: var(--wp--preset--font-size--large);
  --text-x-large: var(--wp--preset--font-size--x-large);
}

Now font-heading text-x-large in a pattern matches the “Extra large” size an editor picks for a heading, including the fluid scaling you set in theme.json. Keep line heights and letter spacing in theme.json’s styles for each element, so headings look right whether they came from a pattern or an editor typed them.

If you design in Figma first

Many designers keep their tokens as Figma variables before anything reaches code. That is a good habit, and it makes this whole process easier if you name things the same way in all three places.

  • Use the same role names in Figma variables, theme.json slugs and Tailwind variables: brand, ink, surface. Not “Primary/500” in Figma and “brand” in code.
  • Group variables by role, then by mode, so light and dark values sit together and map cleanly to the CSS variables.
  • Export once, then treat theme.json as the master. After launch, changes start in the Site Editor or theme.json and flow back to Figma, not the other way round, because that is where the live site reads from.

The point is not the tool. It is that one name means one thing everywhere.

What to hand a developer

If someone else builds the theme, give them three things and you will get back a site that matches your design:

  1. The token table from step 3, with roles, values, which ones are editor swatches, and the contrast rules (such as “dark text on accent”).
  2. The type and spacing scales, with the sizes editors should see and any fluid ranges.
  3. One sentence of policy: “theme.json owns the values; Tailwind reads the WordPress variables; no hex values in markup.”

That last sentence does more than any style guide. It tells the developer where a change belongs, and it makes drift something they would have to go out of their way to create.

Spacing: the token people forget

Colours get all the attention, but spacing is where Tailwind themes and block themes most often disagree. Tailwind v4 builds its spacing utilities from a single base value. WordPress offers editors a set of named spacing steps in the block toolbar. If you leave both alone, the padding an editor picks on a group block will not match the gap your patterns use.

Define a short spacing scale in theme.json, using the steps your designs actually need, and reference those presets in your patterns and in Tailwind:

"spacing": {
  "spacingSizes": [
    { "slug": "20", "name": "Small", "size": "0.5rem" },
    { "slug": "40", "name": "Medium", "size": "1rem" },
    { "slug": "60", "name": "Large", "size": "2rem" },
    { "slug": "80", "name": "Section", "size": "clamp(3rem, 6vw, 6rem)" }
  ]
}

Then map the steps you care about into Tailwind, for example --spacing-section: var(--wp--preset--spacing--80);, so a py-section utility and the editor’s “Section” spacing step are the same size. WordPress’s spacing settings documentation lists the other options, such as generated spacing scales.

Where roles become components: block styles

Tokens are the raw materials. Editors work with components: a button, a card, a callout. In a block theme, the bridge between the two is block styles in theme.json and registered style variations. A “Card” style on the group block can set its background to var(--wp--preset--color--surface), a border in the border shade, and padding from your spacing scale. Editors pick “Card” from the styles panel and never touch a colour.

This is also where your Tailwind utilities and your block styles meet. Use Tailwind inside patterns and templates for layout and fine detail. Use block styles for the handful of named looks editors apply themselves. Both read the same tokens, so a card built from a pattern and a card an editor styled by hand look identical. Our guide to custom block styles as a design system walks through setting these up.

Dark mode with one palette

Roles make dark mode much easier. Because components use “ink”, “base” and “surface” rather than “gray-900” and “white”, you only need to change what those roles mean in dark mode, not every class in every pattern. Set the dark values once, as overrides of the same CSS variables, and every utility and every editor swatch follows. If you used raw shades in your markup instead of roles, this is where you will feel it.

What to do with the rest of the generator’s output

You generated eleven shades per colour and used maybe three. That is fine. Keep the full scale in your design file as a reference. Bring a shade into code only when a component needs it, and give it a role name if editors will ever see it. A smaller token set is easier to maintain, easier to explain to a client and much harder to misuse.

If you are working with AI assistants or other developers, a short, well-named token set also makes your rules easier to follow. We wrote about that in a design system an AI agent can follow.

Common mistakes

  • Putting every generated shade in the editor palette. Editors get thirty blues and pick the wrong one.
  • Naming swatches by colour, so a rebrand leaves a swatch called “Blue” that is now green.
  • Hex values in both theme.json and Tailwind. They will drift. Point Tailwind at the WordPress variables.
  • Skipping contrast checks because the generator’s scale looked smooth.
  • Leaving the default palette on, so editors can still pick colours that are not yours.
  • Ignoring spacing and ending up with padding that never quite matches.

A quick before-and-after

Before: a Tailwind config with fifty colours, a theme.json with WordPress’s defaults plus four brand swatches someone added later, and buttons that use bg-blue-600 while the editor’s “Primary” swatch is a slightly different blue. Every colour change is two edits, and one of them gets forgotten.

After: seven named roles in theme.json, Tailwind reading those roles from WordPress variables, two hover and border shades kept in Tailwind only, and buttons using bg-brand. A rebrand is one edit in Styles. The editor and the front end cannot disagree, because there is only one value to disagree about.

Questions designers ask

What is a Tailwind theme, in WordPress terms?

It is the set of design values Tailwind uses to generate utilities: colours, fonts, sizes, spacing and breakpoints, defined as variables in @theme. In a block theme, the equivalent design values live in theme.json.

Which Tailwind theme generator should I use?

Any good one will give you a usable starting scale. UI Colors and tints.dev are popular. Treat the output as a draft: pin your real brand value, then check contrast before you commit.

Should theme.json or Tailwind own the colours?

For a block theme, theme.json, because it drives the editor controls editors use every day. Tailwind reads the variables WordPress generates.

Can I keep Tailwind’s default colour palette?

You can, but editors will not see it, and your markup may drift towards raw shades instead of roles. Clearing defaults with --color-*: initial keeps everyone on the same set.

Do I need Tailwind at all if theme.json holds the tokens?

Not necessarily. Many block themes use theme.json and block styles alone. Tailwind earns its place when you build a lot of custom patterns and templates and want utility classes for layout. If you use it, let it read from theme.json rather than holding its own copy of the palette.

What if a client wants a colour that is not in the palette?

Treat it as a design decision, not an editor setting. If the colour has a job (a seasonal campaign, a new product line), give it a role name, add it to the token table and to theme.json, and check its contrast. If it does not have a job, the palette is doing its work by saying no. Either way, it changes in one place and every page stays consistent.

How many colours should the editor palette have?

Usually eight to twelve named roles. Enough to build any page on brand, few enough that the choice is obvious.

Where to start

Open your Tailwind theme, or your generator’s export, and write the token table first. Mark which roles editors should see. Then write the palette into theme.json, point Tailwind at the WordPress variables, and run the “turn Brand bright green” test. When the whole site changes colour from one swatch, you have one palette, and it will stay that way. For more on keeping tokens independent of any framework, read why your design tokens should outlive your CSS framework.