Tailwind CSS
Blockstudio includes built-in Tailwind CSS v4 support. When enabled, every frontend page is compiled server-side via TailwindPHP with automatic file-based caching. In the block editor, a bundled Tailwind CDN script provides live preview as you edit.
No build step, no CLI, no Node.js required.
How It Works
Blockstudio uses two separate compilation strategies depending on the context:
Frontend (Server-Side)
On every frontend request, Blockstudio captures the full page HTML through an output buffer, extracts all CSS class candidates, compiles only the CSS that is actually used, and injects the result as an inline <style> tag before </head>.
The compilation flow:
- WordPress renders the full page HTML into an output buffer
TailwindPHP::extractCandidates()scans the HTML and extracts all potential CSS class names (fast regex, no compilation)- The sorted candidates are hashed together with the CSS config to create a cache key
- On cache hit: the compiled CSS is read from disk and injected
- On cache miss:
TailwindPHP::generate()compiles the CSS, writes it to the cache directory, and injects it
The compiled CSS is injected as:
<style id="blockstudio-tailwind">
/* compiled CSS */
</style>
This includes Tailwind’s preflight (CSS reset) and all matched utility classes.
Editor (Client-Side)
Inside the block editor, a bundled Tailwind CDN script (browser build of Tailwind CSS v4) runs client-side. This provides instant live preview as you type class names, without any server round-trips. The CDN is only loaded in the editor and is never served on the frontend.
When assets.reset.enabled is active, Blockstudio also restores Tailwind’s common display and position utilities inside the editor canvas. This keeps wrapper-level classes such as flex, grid, absolute, fixed, and sticky from being overridden by WordPress editor block wrapper rules, so utility-driven layouts match the frontend more closely.
Enabling Tailwind
Add the tailwind configuration to your blockstudio.json:
{
"$schema": "https://blockstudio.dev/schema/blockstudio",
"tailwind": {
"enabled": true
}
}
Or enable it via a filter:
add_filter('blockstudio/settings/tailwind/enabled', '__return_true');
When enabled is true, every frontend page will have Tailwind CSS compiled and injected automatically. You can use Tailwind utility classes anywhere: in block templates, theme templates, the_content filters, or any HTML that appears in the page output.
Frontend output
Compiled CSS is inline by default, preserving the output used by earlier versions. Sites with many shared rules can opt into a content-hashed stylesheet instead:
{
"tailwind": {
"enabled": true,
"output": "link"
}
}
"output": "link" emits the existing
tailwind/{content-hash}.css cache file as a <link rel="stylesheet">. The
URL changes when the runtime identity, candidates, engine, or CSS input
changes, so browsers and edge caches can safely retain old URLs. Configure the
web server or CDN to send a long-lived Cache-Control: public, immutable
header for this cache scope.
The default cache root is public beneath wp-content. If cache.path points
to a private absolute volume, Blockstudio falls back to inline CSS unless the
existing blockstudio/files/url filter supplies a public URL for the
tailwind scope:
add_filter(
'blockstudio/files/url',
function (string $url, string $path, string $scope): string {
if ('tailwind' !== $scope) {
return $url;
}
return 'https://static.example.com/blockstudio/' . basename($path);
},
10,
3
);
Template composition helpers
Blockstudio exposes the same bundled TailwindPHP engine to PHP templates. No theme-side copy or fallback implementation is required:
$classes = bs_tw_merge('px-2 text-sm', 'px-4');
// text-sm px-4
$button = bs_tw_variants([
'base' => 'inline-flex items-center',
'variants' => [
'size' => [
'sm' => 'h-8 px-3',
'lg' => 'h-12 px-6',
],
],
'defaultVariants' => [
'size' => 'sm',
],
]);
echo $button(['size' => 'lg', 'class' => 'rounded']);
bs_tw_merge() accepts nested class values and resolves conflicting Tailwind
utilities. bs_tw_variants() returns a CVA-style callable supporting base,
variants, default variants, compound variants, class, and className.
Configuration
Tailwind v4 uses a CSS-first configuration approach. Instead of a JavaScript config file, you write configuration directly as CSS using the @theme directive.
{
"tailwind": {
"enabled": true,
"config": "@theme { --color-primary: #3b82f6; --color-secondary: #10b981; --font-family-heading: 'Inter', sans-serif; }"
}
}
The config value is a CSS string that gets appended after the @import "tailwindcss" directive. You can use any valid Tailwind v4 CSS syntax here:
{
"tailwind": {
"enabled": true,
"config": "@theme { --color-brand: oklch(0.7 0.15 200); } @layer base { h1 { font-size: var(--text-4xl); } }"
}
}
For multiline configs, the filter approach is more ergonomic:
add_filter('blockstudio/settings/tailwind/config', function () {
return <<<CSS
@theme {
--color-primary: #3b82f6;
--color-secondary: #10b981;
--font-family-heading: 'Inter', sans-serif;
--breakpoint-xs: 30rem;
}
@layer base {
body {
font-family: var(--font-family-sans);
}
}
@layer utilities {
.btn { @apply px-4 py-2 rounded-lg font-medium; }
.btn-primary { @apply bg-blue-500 text-white hover:bg-blue-600; }
.card { @apply bg-white rounded-xl shadow-lg p-6; }
}
CSS;
});
Custom Utility Classes
To define reusable class aliases, use @layer utilities with Tailwind’s @apply directive directly in your config string:
{
"tailwind": {
"enabled": true,
"config": "@theme { --color-brand: #6366f1; } @layer utilities { .btn { @apply px-4 py-2 rounded-lg font-medium; } .section { @apply py-16 px-4 max-w-7xl mx-auto; } }"
}
}
This compiles these classes alongside all standard Tailwind utilities. They work both on the frontend (server-side compilation) and in the editor (CDN preview).
Blockstudio preserves Tailwind’s canonical cascade layer order:
theme, base, components, then utilities. This matches Tailwind CSS, so
rules in later layers can override earlier layers as expected.
Classes Field Type
To use Tailwind classes in a block’s classes field with autocomplete support, set tailwind: true on the field:
{
"name": "my-theme/hero",
"title": "Hero",
"blockstudio": {
"attributes": [
{
"id": "classes",
"type": "classes",
"label": "Classes",
"tailwind": true,
"default": "bg-white text-gray-900"
}
]
}
}
When tailwind is true:
- The field provides autocomplete suggestions from the full Tailwind class list
- The Tailwind CDN script is loaded in the editor for live preview
- Classes are stored as a space-separated string in the block attributes
Use the classes value in your template:
<div class="<?php echo $a['classes']; ?>">
<!-- block content -->
</div>
Settings Reference
| Option | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | false |
Enable Tailwind CSS compilation on the frontend |
config |
string | "" |
Tailwind v4 CSS-first configuration string |
Setting via JSON
{
"tailwind": {
"enabled": true,
"config": "@theme { --color-brand: pink; }"
}
}
Setting via Filters
add_filter('blockstudio/settings/tailwind/enabled', '__return_true');
add_filter('blockstudio/settings/tailwind/config', function () {
return '@theme { --color-brand: pink; }';
});
Caching
Blockstudio uses a custom caching strategy designed for Tailwind’s utility-based architecture.
How the Cache Works
Rather than hashing the entire page HTML (which would bust the cache on every request due to nonces, timestamps, and other dynamic content), Blockstudio extracts only the CSS class candidates from the HTML. These candidates are sorted and hashed together with the CSS configuration to produce a stable cache key.
This means:
- Two pages that use the same set of Tailwind classes share the same cached CSS file, even if their HTML is completely different
- Dynamic content like nonces, logged-in user bars, comment counts, and timestamps do not bust the cache
- The cache only invalidates when the set of CSS classes on a page changes or the Tailwind config changes
When a runtime uses a logical discovery source, the selected source and runtime context also participate in the cache namespace. Last-good CSS is isolated between previews and cannot leak from one source selection into another.
Cache Location
Cache files are stored in:
wp-content/blockstudio/cache/sites/{network-blog}/{runtime-identity}/tailwind/
The configured cache.path, current multisite identity, and complete runtime
identity determine the exact prefix. Each file is named with an MD5 hash:
{hash}.css.
Clearing the Cache
Delete the contents of the cache directory to force recompilation:
// Clear all cached Tailwind CSS
Blockstudio\Runtime_Cache::purge('tailwind');
Or via WP-CLI:
wp eval "Blockstudio\\Runtime_Cache::purge('tailwind');"
The next frontend request will recompile and cache the CSS automatically.
Filters
blockstudio/tailwind/css
Modify the CSS input before compilation. The CSS input string starts with @import "tailwindcss";, followed by any config. Use this filter to add additional CSS directives, custom @layer rules, or plugin imports.
add_filter('blockstudio/tailwind/css', function (string $css): string {
// Add a custom utility
$css .= "\n@utility container-narrow { max-width: 48rem; margin-inline: auto; }";
return $css;
});
The full CSS input that gets compiled looks like this:
/* Base import */
@import 'tailwindcss';
/* Config from settings (if set) */
@theme {
--color-brand: pink;
}
/* Anything added via the blockstudio/tailwind/css filter */
Output Buffer
Tailwind compilation hooks into Blockstudio’s output buffer system. The buffer captures the complete page HTML after WordPress has finished rendering, allowing Tailwind to scan all classes from every source: block templates, theme templates, plugin output, the_content filters, and widget areas.
The compilation filter runs at priority 999999 on the blockstudio/buffer/output hook, ensuring it processes the final HTML after all other modifications. The buffer is started on the template_redirect action, which means it only runs on frontend requests, not in the admin, REST API, or AJAX contexts.
Buffering the whole document is what makes this possible, so it is on by default. A site that uses neither Tailwind nor block assets can turn it off:
add_filter( 'blockstudio/buffer/enabled', '__return_false' );
Returning false skips the buffer entirely, which also disables Tailwind compilation and the hoisting of block styles and scripts into the head and footer.
Architecture
Frontend Request
│
├─ WordPress renders HTML into output buffer
│
├─ blockstudio/buffer/output filter (priority 999999)
│ │
│ ├─ Settings::get('tailwind/enabled') → false? Return HTML unchanged
│ │
│ ├─ build_css_input()
│ │ ├─ @import "tailwindcss"
│ │ ├─ + tailwind/config setting
│ │ └─ + blockstudio/tailwind/css filter
│ │
│ ├─ TailwindPHP::extractCandidates($html)
│ │ └─ Returns array of CSS class names found in HTML
│ │
│ ├─ Cache key = md5(sorted candidates + css input)
│ │
│ ├─ Cache hit?
│ │ ├─ Yes → Read CSS from the shared runtime `tailwind/{hash}.css` scope
│ │ └─ No → TailwindPHP::generate() → Write to cache file
│ │
│ └─ Inject inline CSS, or link the content-hashed cache file when tailwind.output is "link"
│
└─ Browser receives HTML with compiled CSS
Editor Request
│
├─ Admin loads block editor
│
├─ Block with classes field (tailwind: true) detected
│ └─ Block_Registry::set_tailwind_active(true)
│
├─ Admin passes isTailwindActive + tailwindUrl to JS
│
└─ useTailwind() hook
├─ Injects Tailwind CDN script into document
├─ Creates hidden template div with HTML content
├─ Creates style tag for config
└─ CDN compiles CSS in-browser for live preview
Full Example
A complete working setup with custom theme colors and a block that uses them:
{
"$schema": "https://blockstudio.dev/schema/blockstudio",
"tailwind": {
"enabled": true,
"config": "@theme { --color-brand: #6366f1; --color-brand-light: #a5b4fc; --color-surface: #f8fafc; } @layer utilities { .section-padding { @apply py-20 px-6; } .content-width { @apply max-w-5xl mx-auto; } .heading-xl { @apply text-4xl font-bold tracking-tight; } }"
}
}
{
"name": "my-theme/hero",
"title": "Hero Section",
"blockstudio": {
"attributes": [
{
"id": "title",
"type": "text",
"label": "Title",
"default": "Welcome"
},
{
"id": "wrapperClasses",
"type": "classes",
"label": "Wrapper Classes",
"tailwind": true,
"default": "section-padding bg-surface"
},
{
"id": "titleClasses",
"type": "classes",
"label": "Title Classes",
"tailwind": true,
"default": "heading-xl text-brand"
}
]
}
}
<section class="<?php echo $a['wrapperClasses']; ?>">
<div class="content-width">
<h1 class="<?php echo $a['titleClasses']; ?>">
<?php echo esc_html($a['title']); ?>
</h1>
</div>
</section>
This renders on the frontend with all Tailwind utilities and custom theme colors compiled into a single inline <style> tag.
Design tokens, custom utilities, and the complete Tailwind v4 setup for a production theme.