Tailwind CSS CDN on a WordPress Site: When It Is Fine and When to Add a Build Step

You want to try Tailwind on a WordPress site, and the fastest route is one script tag. Paste the Tailwind CSS CDN into the head, add a few utility classes to a template or a block, and the page restyles in front of you. No Node, no build, no config. For a prototype, a client mockup or an internal tool, that feels like magic.
Then the questions start. Is it fine to leave it there? Why does the block editor not show the same styles? Why does the page flash unstyled for a moment on slow phones? And how does any of this sit with theme.json, which is supposed to be the source of truth for a block theme’s design?
This is a decision guide for theme authors and site builders. It covers what the CDN actually does, when it is a perfectly sensible choice, when to switch to a build step, and how to make that switch without fighting the block editor or theme.json.
What the Tailwind CSS CDN actually is
Tailwind calls it the Play CDN. In Tailwind v4 it is a single script:
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
That script runs in the visitor’s browser. It reads the page, finds the utility classes in your markup, and generates the matching CSS on the spot. You can even write Tailwind CSS directly in a style tag with type="text/tailwindcss", including a @theme block for your own colours.
Tailwind’s own documentation is direct about the purpose. The Play CDN page says it is “designed for development purposes only, and is not intended for production.” That is the whole decision in one sentence, but the reasons matter, because there are cases on WordPress where “development” covers more than you might think.
The build step, in one paragraph
The alternative is the normal way Tailwind works: a tool scans your project files for class names ahead of time and writes a static CSS file containing only the classes you used. The Tailwind CLI is the simplest version:
npm install tailwindcss @tailwindcss/cli
npx @tailwindcss/cli -i ./src/input.css -o ./assets/css/tailwind.css --watch
The output is a plain CSS file. You enqueue it like any other stylesheet. There is no JavaScript at runtime, nothing to download from a third party, and nothing to compute in the visitor’s browser. The CLI is also available as a standalone executable if you do not want Node on your machine.
CDN vs build step at a glance
| Play CDN | Build step | |
|---|---|---|
| Setup time | One script tag | Install the CLI, add an input CSS file, run a command |
| Where CSS is generated | In each visitor’s browser, every page load | Once, on your machine or in your deploy |
| JavaScript needed by visitors | Yes | No |
| Flash of unstyled content | Possible on slow devices | No, CSS loads like any stylesheet |
| Third-party dependency at runtime | Yes, the CDN | No |
| Works with a strict Content Security Policy | Awkward | Yes |
| Classes typed by editors in the block editor | Picked up automatically | Only if you safelist them |
| Tailwind’s own guidance | Development only | Production |
Notice the second-to-last row. That is the WordPress-specific wrinkle, and it is why people get stuck. More on it below.
When the CDN is fine
Using the CDN on WordPress is reasonable when the page is not something the public depends on:
- Prototyping a theme. You are sketching layouts in templates and patterns and want instant feedback before you commit to a structure.
- Client mockups on a staging site. Nobody but you and the client will see it, and speed of iteration matters more than performance.
- Internal tools and dashboards behind a login, with a handful of users on good connections.
- Learning Tailwind. You want to see what utilities do without learning a build tool at the same time.
- A one-off landing page experiment you plan to throw away or rebuild properly if it works.
In all of these, the costs (runtime JavaScript, a moment of unstyled content, a third-party script) do not hurt anyone who matters. If you want it in a theme for prototyping, enqueue it rather than hard-coding it in a template, so you can remove it in one place later:
add_action( 'wp_enqueue_scripts', function () {
if ( wp_get_environment_type() !== 'production' ) {
wp_enqueue_script(
'tailwind-play',
'https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4',
array(),
null,
false
);
}
} );
The environment check keeps it off the live site even if the code ships by accident. Set WP_ENVIRONMENT_TYPE in wp-config.php on staging and local so the check does its job.
When to add a build step
Switch to a build step the moment any of these are true:
- Real visitors use the site. Every one of them downloads and runs the Tailwind script and generates CSS on their device. On a mid-range phone on a slow connection, that shows.
- You care about Core Web Vitals. A stylesheet that only exists after JavaScript runs is the opposite of what a fast first paint needs.
- The site has a Content Security Policy, or you are about to add one. Runtime-generated styles and third-party scripts make a strict policy harder.
- You distribute the theme. A theme that loads a third-party script on every page of someone else’s site is not a theme you want your name on.
- You want the block editor to match the front end. A build step gives you one CSS file you can load in both places.
For most public WordPress sites, that means the CDN is a starting point, not a destination.
The WordPress wrinkle: classes that live in the database
Here is the part most guides skip. A build step only knows about classes it can find in files. Tailwind’s source detection scans your project files as plain text and generates CSS for the class names it finds.
On a WordPress site, a lot of markup does not live in files. When an editor selects a block and types rounded-xl shadow-lg into Advanced, Additional CSS class(es), that class name is stored in the post content in the database. The build step never sees it. The CDN, by contrast, reads the live page, so editor-typed classes just work. That is why a site that looked perfect on the CDN can lose styles after you switch.
You have three ways to handle it.
Option 1: keep Tailwind classes out of post content
Use Tailwind in your templates, template parts and patterns, which are files the build step can scan. Give editors design choices through theme.json and block styles instead of raw utility classes. This is the cleanest option for a block theme, and it is how most well-built themes work: editors pick “Card” or “Highlighted” from the block styles panel, and the theme decides what that means in CSS. Our guide to custom block styles as a design system covers that approach.
Option 2: safelist the classes editors may use
Tailwind v4 lets you force classes into the output with @source inline(), and it supports brace expansion, so you can safelist a family of classes in one line:
@import "tailwindcss";
@source inline("{hover:,}bg-brand-{50,{100..900..100}}");
@source inline("rounded-{md,lg,xl} shadow-{sm,md,lg}");
This works well when you can give editors a short, documented list of allowed classes. It does not scale to “any Tailwind class an editor might think of”.
Option 3: scan exported content
Some teams export post content to files as part of their deploy and add that folder as a source. It works, but it couples your CSS build to your content, and a new post can still need a rebuild. Treat it as a last resort.
Pointing the build at a block theme’s files
By default Tailwind v4 scans everything in the working directory except ignored paths. In a theme, be explicit so the build picks up templates, parts and patterns, and ignores folders like vendor code:
@import "tailwindcss" source(none);
@source "../templates";
@source "../parts";
@source "../patterns";
@source "../inc";
Templates and parts in a block theme are HTML files with block markup. Patterns are PHP files. Tailwind reads all of them as plain text, which is exactly what you want. One rule from Tailwind’s docs to remember: never build class names by gluing strings together in PHP, such as 'bg-' . $color . '-600'. The scanner cannot see a class that only exists after PHP runs. Write complete class names and pick between them.
Preflight vs WordPress core styles
Tailwind ships a base reset called Preflight. On a plain HTML page it is helpful. In WordPress, it can strip spacing and list styles that core blocks and theme.json expect, which shows up as oddly flat headings, lists without bullets and buttons that lose their look.
Tailwind documents how to turn Preflight off by importing only the parts you need. The Preflight docs show it:
@layer theme, base, components, utilities;
@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/utilities.css" layer(utilities);
For a block theme, this is usually the right default: theme.json and core handle base styles, Tailwind provides utilities. If you do want Preflight, test core blocks carefully, especially lists, headings, buttons and the navigation block.
Loading the built CSS on the front end and in the editor
Once you have a built file, load it in both places so editors see what visitors see. The enqueue_block_assets hook runs for the front end and for the block editor’s content area, which makes it a good fit:
add_action( 'enqueue_block_assets', function () {
$path = get_theme_file_path( 'assets/css/tailwind.css' );
if ( file_exists( $path ) ) {
wp_enqueue_style(
'theme-tailwind',
get_theme_file_uri( 'assets/css/tailwind.css' ),
array(),
filemtime( $path )
);
}
} );
Using the file’s modified time as the version means browsers fetch the new file after every build. The file_exists check stops the theme from requesting a stylesheet that has not been built yet, which would otherwise show up as a 404 in the browser console.
Keeping one palette: Tailwind and theme.json
A Tailwind theme and a WordPress block theme both want to own your colours, spacing and fonts. If they disagree, the editor colour picker offers one blue and your utilities use another. Pick one source of truth and feed the other from it.
The approach we prefer for block themes: keep design tokens in theme.json, so the editor’s colour, font and spacing controls use them, then point Tailwind’s @theme at the CSS variables WordPress generates from theme.json. WordPress outputs presets as custom properties such as --wp--preset--color--brand:
@theme {
--color-brand: var(--wp--preset--color--brand);
--color-ink: var(--wp--preset--color--ink);
--spacing-section: var(--wp--preset--spacing--60);
}
Now bg-brand in a pattern and the “Brand” swatch in the editor are the same colour by construction. Change it once in theme.json and both follow. Our sister site goes deeper on the developer side in bridging Tailwind v4 and theme.json, and we cover why tokens should not be tied to any one CSS framework in your design tokens should outlive your CSS framework.
A worked example: one pattern, both worlds
Say you are building a “feature row” pattern for a block theme: a heading, a short paragraph and three cards. On the CDN you might write it straight into a pattern file with utility classes on group blocks. With a build step the same file works unchanged, because the pattern lives in patterns/, which the build scans:
<?php
/**
* Title: Feature row
* Slug: brndle-demo/feature-row
* Categories: featured
*/
?>
<!-- wp:group {"className":"grid gap-6 md:grid-cols-3"} -->
<div class="wp-block-group grid gap-6 md:grid-cols-3">
<!-- wp:group {"className":"rounded-xl bg-brand/5 p-6"} -->
<div class="wp-block-group rounded-xl bg-brand/5 p-6"></div>
<!-- /wp:group -->
</div>
<!-- /wp:group -->
Two details make this hold up. The utility classes are complete strings in a file, so the scanner finds them. And bg-brand resolves to the theme.json preset through the @theme mapping above, so the cards change colour when someone edits the palette in the Site Editor. The pattern stays a normal block pattern: editors can insert it, change the text and move the cards, without ever typing a class name.
Dark mode without two sources of truth
Tailwind’s dark: variant follows the visitor’s system setting through prefers-color-scheme by default. If your theme offers its own light and dark switch, Tailwind’s dark mode docs show how to drive the variant from a class instead:
@import "tailwindcss";
@custom-variant dark (&:where(.dark.dark *));
In a block theme, a cleaner route is often to avoid dark: utilities for colour altogether. Keep colours as theme.json presets that map to CSS variables, switch the variable values for dark mode in one place, and let every utility that uses bg-brand or text-ink follow automatically. One palette, two modes, no duplicated classes in your markup.
Shipping the built CSS
Once you have a build step, decide where the build runs. There is no single right answer, but there is a wrong one: a theme that only works if whoever installs it remembers to run a command.
- A theme you distribute should include the built
tailwind.cssin the zip. Users should never need Node to install a theme. - A client site you deploy can build in your deploy pipeline, or commit the built file. Committing is simpler and makes every deploy predictable; building in the pipeline keeps the repository tidy. Pick one and write it in the README.
- A team should run the same Tailwind version. Pin it in
package.jsonso one person’s update does not quietly change everyone’s CSS.
Whichever you choose, rebuild before you release, and check the editor as well as the front end. A missing class is much easier to spot on staging than in a client’s inbox.
Moving a CDN prototype to a build step, in order
- List the classes in use. Search templates, parts and patterns, and check posts for Additional CSS classes. Note any class that only appears in post content.
- Add an input CSS file with the Tailwind import (with or without Preflight), your
@sourcepaths and your@themeblock. - Move any
text/tailwindcssstyle blocks from the head into that file. - Safelist editor classes with
@source inline(), or replace them with block styles. - Build once and enqueue the output with
enqueue_block_assets. - Remove the CDN script and check every template, plus the editor, at desktop and phone widths.
- Add the build to your workflow: a watch command while you work, and a one-off build in your deploy or before you zip the theme.
If a style disappears after step 6, the class is either built dynamically somewhere or only exists in post content. Those are the two places to look first.
Performance, honestly
It is easy to overstate the CDN’s cost on a single page view on a fast laptop. The browser script is quick, and the CDN itself is reliable. The cost shows up in the cases that matter most for real sites: first visits on mid-range phones, slow mobile networks, and pages where the first paint decides whether someone stays. A static stylesheet only containing the classes you use is about as cheap as CSS gets. If you are going to measure, test a real page on a throttled mobile profile, not your development machine.
A pre-launch checklist
Before a Tailwind-styled WordPress site goes live, run through this once:
- View the page source and confirm there is no
@tailwindcss/browserscript. - Open the built stylesheet and check its size is sensible for the classes you use.
- Open three templates and three patterns in the Site Editor and compare them with the front end.
- Edit a post that uses Additional CSS classes and check those styles survived the build.
- Change one colour in Styles and confirm both the editor swatch and the utilities follow it.
- Test at phone width, with the browser’s network throttled to a slow mobile profile.
- Switch the system to dark mode, or use your theme’s toggle, and check contrast.
Seven checks, about twenty minutes, and they catch nearly every surprise that turns up after switching from the CDN.
Common mistakes
- Shipping the CDN to production because it worked on staging.
- Building class names in PHP from variables. The scanner cannot see them.
- Forgetting editor-typed classes when switching to a build step.
- Two palettes: colours in Tailwind config that do not match theme.json presets.
- Keeping Preflight without testing core blocks, then wondering why lists lost their bullets.
- Loading the CSS only on the front end, so the editor looks nothing like the site.
Questions theme builders ask
Can I use the Tailwind CSS CDN on a live WordPress site?
It will work, but Tailwind’s own documentation says the Play CDN is for development only. For a public site, use a build step and load a static stylesheet.
Does Tailwind work with block themes?
Yes. Use it for utilities in templates, parts and patterns, keep design tokens in theme.json, and map Tailwind’s theme to the CSS variables WordPress generates.
Why do my styles disappear after switching from the CDN?
Usually because the classes are only in post content (typed into the block editor) or are built dynamically in PHP. Safelist them with @source inline() or replace them with block styles.
Should I turn off Preflight?
In most block themes, yes. Let theme.json and core handle base styles and use Tailwind for utilities. If you keep Preflight, test core blocks carefully.
Does any of this apply to classic themes?
Yes. The CDN versus build decision is the same. The differences are where you point @source (your PHP template files instead of block templates and patterns) and how you load the CSS in the classic editor, where add_editor_style() is the usual route. The database wrinkle applies too: classes typed into post content are invisible to the build unless you safelist them.
Do I need Node to build Tailwind?
Not necessarily. Tailwind offers a standalone CLI executable that runs without Node.js.
The short answer
Use the Tailwind CSS CDN to try ideas, prototype and learn, on staging or behind a login. Switch to a build step before real visitors arrive. On WordPress, the switch is easy if you plan for the one difference that catches everyone: classes that live in the database. Keep tokens in theme.json, point Tailwind at them, load one stylesheet in the front end and the editor, and you get Tailwind’s speed of building without paying for it on every page view. If you are still choosing a framework at all, our comparison of the best CSS frameworks in 2026 is a good place to start, and design tokens in theme.json covers the token side in more depth.