Tailwind CSS 76 🎨 Server-Driven UI (SDUI) Utility Ingestion and Dynamic Runtime Styling
A server-driven UI reverses the usual direction of a front-end build. Instead of shipping the markup and the styles from the client bundle, the server sends a description of the interface, and the client renders it. The description may be a JSON tree, a set of components, or a list of the Tailwind class strings. The client interprets the description and produces the DOM. The model is the mobile-app pattern, the CMS-driven page builder, and the A/B testing platform. The problem is the same: the Tailwind engine scans the source at the build time, and the class from the server is not in the source. The client must have the CSS for the class it will render.
Key point: The Tailwind build-time engine does not exist at the runtime. The JIT scan is the build step, and the class that the server sends must be the class that the build produced. The runtime styling is about the mechanism that gets the server’s class into the CSS that the client already has, or the mechanism that generates the CSS at the runtime.
Why SDUI exists
The deploy problem. The new feature requires the new build, and the build requires the app-store review. The SDUI lets the server change the interface without the client deploy.
The personalization problem. The different users see the different layouts. The server chooses the layout, and the client renders it. The experiment and the segmentation are the server-side.
The A/B testing problem. The variant is the server’s decision. The client renders the variant from the description. The change is the immediate, and the measurement is the server’s.
The CMS problem. The content editor arranges the blocks and the layout. The block is the component, and the layout is the server’s data. The client renders the arrangement.
The build-time problem. The Tailwind engine scans the source. The class from the server is not in the source, and the class is not in the CSS. The runtime must have the mechanism.
The allowlist problem. The safe approach is the allowlist: the server may only use the classes the build included. The list is the contract, and the client renders only the known classes.
The runtime generation problem. The JIT at the runtime parses the class, looks up the theme, and injects the CSS. The approach is the flexible, and the cost is the bundle and the performance.
a. The allowlist
The allowlist is the set of the classes the build includes. The server may only use the classes in the set. The client renders the known classes.
The @source inline
The Tailwind v4 has the @source inline(...) that forces the class into the build.
@import "tailwindcss";
@source inline("bg-blue-600");
@source inline("bg-red-600");
@source inline("bg-green-600");
@source inline("text-white");
@source inline("px-4 py-2 rounded-md");
The classes are the safelisted, and the build produces the CSS.
The safe list from the config
The v4 supports the brace expansion in the @source inline.
@source inline("bg-{red,blue,green}-{500,600,700}");
@source inline("text-{sm,base,lg,xl}");
The brace expands to the many classes. The bg-red-500, the bg-red-600, the bg-red-700, and the same for the blue and the green.
The allowlist file
The allowlist is the JSON file the server and the client share.
// allowlist.json
{
"colors": ["red-500", "red-600", "blue-500", "blue-600"],
"spacing": ["1", "2", "4", "8"],
"radius": ["sm", "md", "lg"]
}
The server composes the class from the allowed parts, and the client renders the class.
The server validation
The server validates the class against the allowlist before sending.
function validateClasses(classes) {
return classes.every((cls) => allowlist.includes(cls));
}
const classes = ['bg-blue-600', 'px-4'];
if (!validateClasses(classes)) {
throw new Error('The class is not in the allowlist');
}
The validation is the contract, and the server rejects the unknown.
The client rendering
function ServerComponent({ type, className, children }) {
return <div className={className}>{children}</div>;
}
The className from the server is applied directly. The CSS is already in the bundle.
The allowlist table
| The mechanism | The purpose |
|---|---|
@source inline(...) | The safelist |
| The brace expansion | The many classes |
| The allowlist JSON | The shared contract |
| The server validation | The reject the unknown |
| The client render | The apply the class |
b. The runtime generation
The runtime generation parses the class and injects the CSS. The approach is the flexible, and the cost is the bundle.
The twind
The twind is the runtime Tailwind. It parses the class and injects the CSS at the runtime.
import { twind, cssom, observe } from '@twind/core';
import presetTailwind from '@twind/preset-tailwind';
const tw = twind({
presets: [presetTailwind()],
}, cssom(document));
observe(tw, document.body);
The twind parses the classes in the DOM and injects the matching CSS.
The class parsing
const css = tw('bg-blue-600 px-4 py-2');
// The CSS is the generated and injected.
The tw function returns the class name, and the CSS is the injected.
The performance cost
The runtime generation adds the JavaScript bundle (the twind is the roughly 15 KB) and the parsing at the runtime. The cost is the price of the flexibility.
The @twind/preset-tailwind
The preset is the Tailwind-compatible. The @twind/preset-tailwind is the v3-compatible, and the @twind/preset-tailwind-forms is the plugin.
The React integration
import { tw } from './twind';
function Component() {
return <div className={tw('bg-blue-600 p-4')}>...</div>;
}
The tw returns the class, and the runtime injects the CSS.
The style injection
The cssom and the observe inject the CSS into the document. The twind watches the DOM and the generates the CSS for the new classes.
The runtime table
| The tool | The purpose |
|---|---|
twind | The runtime Tailwind |
@twind/core | The core |
@twind/preset-tailwind | The preset |
cssom | The style injection |
observe | The DOM watch |
c. The hybrid and the constraints
The hybrid combines the build-time and the runtime. The build includes the common, and the runtime generates the rare.
The build-time common
@import "tailwindcss";
@source inline("bg-{red,blue,green}-{500,600}");
@source inline("text-{sm,base,lg}");
The common classes are the safelisted, and the build produces the CSS.
The runtime rare
const runtimeClasses = serverClasses.filter((cls) => !buildClasses.includes(cls));
const runtimeCSS = tw(runtimeClasses.join(' '));
The rare classes are the generated at the runtime. The common is the build.
The constraints
The runtime generation has the constraints: the oklch() and the modern functions are not supported in the old browsers, the custom variant may not work, and the arbitrary values are the parsed at the runtime.
The CSP problem
The runtime style injection may violate the Content Security Policy. The style-src 'unsafe-inline' is required, and the CSP is the weaker.
The SSR problem
The server renders the HTML, and the client hydrates. The runtime generation on the server and the client must match, and the mismatch causes the hydration error.
The cache problem
The runtime classes are not in the build cache. The repeated class is the parsed each time, and the twind caches the result.
The hybrid table
| The aspect | The build-time | The runtime |
|---|---|---|
| The common | The build | — |
| The rare | — | The runtime |
| The bundle | The CSS | The JS |
| The CSP | The strict | The unsafe-inline |
| The SSR | The stable | The mismatch risk |
Complete Example Session
/* ============================================
PART 1: THE ALLOWLIST
============================================ */
@import "tailwindcss";
@source inline("bg-blue-600");
@source inline("bg-red-600");
@source inline("bg-green-600");
@source inline("text-white");
@source inline("px-4 py-2 rounded-md");
/* ============================================
PART 2: THE BRACE EXPANSION
============================================ */
@source inline("bg-{red,blue,green}-{500,600,700}");
@source inline("text-{sm,base,lg,xl}");
/* ============================================
PART 3: THE ALLOWLIST JSON
============================================ */
{
"colors": ["red-500", "red-600", "blue-500", "blue-600"],
"spacing": ["1", "2", "4", "8"],
"radius": ["sm", "md", "lg"]
}
/* ============================================
PART 4: THE SERVER VALIDATION
============================================ */
function validateClasses(classes) {
return classes.every((cls) => allowlist.includes(cls));
}
const classes = ['bg-blue-600', 'px-4'];
if (!validateClasses(classes)) {
throw new Error('The class is not in the allowlist');
}
/* ============================================
PART 5: THE CLIENT RENDER
============================================ */
function ServerComponent({ type, className, children }) {
return <div className={className}>{children}</div>;
}
/* ============================================
PART 6: THE TWIND SETUP
============================================ */
import { twind, cssom, observe } from '@twind/core';
import presetTailwind from '@twind/preset-tailwind';
const tw = twind({
presets: [presetTailwind()],
}, cssom(document));
observe(tw, document.body);
/* ============================================
PART 7: THE RUNTIME CLASS
============================================ */
const css = tw('bg-blue-600 px-4 py-2');
// The CSS is generated and injected.
/* ============================================
PART 8: THE REACT INTEGRATION
============================================ */
import { tw } from './twind';
function Component() {
return <div className={tw('bg-blue-600 p-4')}>...</div>;
}
/* ============================================
PART 9: THE HYBRID
============================================ */
const runtimeClasses = serverClasses.filter((cls) => !buildClasses.includes(cls));
const runtimeCSS = tw(runtimeClasses.join(' '));
/* ============================================
PART 10: THE COMPLETE PATTERN
============================================ */
// 1. The build scans the source, not the server.
// 2. The server's class is not in the source.
// 3. The allowlist is the contract.
// 4. The @source inline forces the class.
// 5. The runtime generation is the flexible.
// 6. The twind is the runtime Tailwind.
// 7. The CSP is the constraint.
// 8. The SSR must match.
// 9. The hybrid combines the both.
// 10. The validation is the server.
The ten parts showed the allowlist, the brace expansion, the allowlist JSON, the server validation, the client render, the twind setup, the runtime class, the React integration, the hybrid, and the complete pattern.
Quick Reference
The Allowlist
| The mechanism | The purpose |
|---|---|
@source inline(...) | The safelist |
| The brace expansion | The many classes |
| The allowlist JSON | The contract |
| The server validation | The reject |
| The client render | The apply |
The Runtime Tools
| The tool | The purpose |
|---|---|
twind | The runtime Tailwind |
@twind/core | The core |
@twind/preset-tailwind | The preset |
cssom | The injection |
observe | The DOM watch |
The Constraints
| The aspect | The build | The runtime |
|---|---|---|
| The bundle | The CSS | The JS |
| The CSP | The strict | The unsafe-inline |
| The SSR | The stable | The mismatch |
| The cache | The build | The runtime |
| The flexibility | The low | The high |
The Patterns
| The pattern | The mechanism |
|---|---|
| The pure allowlist | The @source inline |
| The pure runtime | The twind |
| The hybrid | The both |
| The validation | The server |
The CSP Considerations
| The directive | The value |
|---|---|
style-src | The 'unsafe-inline' |
| The nonce | The alternative |
| The hash | The alternative |
| The strict | The runtime disabled |
Best Practices
✅ Do This:
/* Use the @source inline for the allowlist. */
@source inline("bg-{red,blue,green}-{500,600}"); /* ✅ */
// Validate the server's class.
if (!validateClasses(classes)) throw new Error('The unknown class'); // ✅
// Use the twind for the runtime generation.
const tw = twind({ presets: [presetTailwind()] }, cssom(document)); // ✅
// Use the hybrid for the common and the rare.
const runtimeClasses = serverClasses.filter((cls) => !buildClasses.includes(cls)); // ✅
// Apply the server's class directly.
<div className={className}>{children}</div>; // ✅
// Use the CSP nonce for the runtime styles.
const nonce = getNonce(); // ✅
❌ Don’t Do This:
// Don't expect the build to include the server's class.
<div className={serverClass}>; // ❌ The CSS is not generated
// Don't skip the validation.
sendClass(serverClass); // ❌ The unknown class
// Don't use the runtime generation without the reason.
const tw = twind(...); // ⚠️ The bundle and the CSP
// Don't rely on the CSP unsafe-inline.
style-src 'unsafe-inline'; // ❌ The weaker
// Don't mismatch the SSR and the client.
// The hydration error. // ❌
// Don't generate the class at the runtime for the common.
tw('bg-blue-600'); // ⚠️ The build should include it
Common Pitfalls
| The pitfall | The reason | The fix |
|---|---|---|
| The class has no CSS | Not in the source | Use the @source inline |
| The class rejected | Not in the allowlist | Add to the allowlist |
| The CSP blocks | The runtime injection | Use the nonce |
| The hydration mismatch | The SSR differs | Match the both |
| The bundle grows | The runtime | Use the hybrid |
| The cache miss | The runtime | Use the build |
| The validation passes | No check | Add the server validation |
Real-World Examples
1. The @source inline
@source inline("bg-blue-600");
2. The Brace Expansion
@source inline("bg-{red,blue,green}-{500,600}");
3. The Allowlist JSON
{ "colors": ["red-500", "blue-500"] }
4. The Server Validation
if (!validateClasses(classes)) throw new Error('Unknown');
5. The Client Render
<div className={className}>{children}</div>
6. The Twind Setup
const tw = twind({ presets: [presetTailwind()] }, cssom(document));
7. The Runtime Class
const css = tw('bg-blue-600 px-4 py-2');
8. The React Integration
<div className={tw('bg-blue-600 p-4')}>
9. The Hybrid
const runtimeClasses = serverClasses.filter((cls) => !buildClasses.includes(cls));
10. The Complete Pattern
@source inline("bg-{red,blue,green}-{500,600}");
Visual
The SDUI Flow
┌────────────────────────────────────────────────────────────┐
│ THE SDUI FLOW │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ THE SERVER │ │
│ │ The JSON: { "className": "bg-blue-600 p-4" } │ │
│ └────────────────────────┬─────────────────────────┘ │
│ │ │
│ │ The response │
│ v │
│ ┌──────────────────────────────────────────────────┐ │
│ │ THE CLIENT │ │
│ │ The render: <div className="bg-blue-600 p-4"> │ │
│ └────────────────────────┬─────────────────────────┘ │
│ │ │
│ │ The CSS lookup │
│ v │
│ ┌──────────────────────────────────────────────────┐ │
│ │ THE CSS │ │
│ │ The build produced the .bg-blue-600. │ │
│ │ The build produced the .p-4. │ │
│ │ The allowlist guarantees the class. │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
The Allowlist
┌────────────────────────────────────────────────────────────┐
│ THE ALLOWLIST │
│ │
│ THE BUILD │
│ ┌──────────────────────────────────────────────────┐ │
│ │ @source inline("bg-{red,blue,green}-{500,600}"); │ │
│ │ → the .bg-red-500, the .bg-red-600, ... │ │
│ │ → the CSS is in the bundle. │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ THE SERVER │
│ ┌──────────────────────────────────────────────────┐ │
│ │ The class: "bg-blue-600 p-4" │ │
│ │ → the validation: in the allowlist? │ │
│ │ → the response: { className: "..." } │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ THE CLIENT │
│ ┌──────────────────────────────────────────────────┐ │
│ │ <div className="bg-blue-600 p-4"> │ │
│ │ → the CSS is already in the bundle. │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
The Runtime Generation
┌────────────────────────────────────────────────────────────┐
│ THE RUNTIME GENERATION │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ const tw = twind({ presets: [presetTailwind()] });│ │
│ │ │ │
│ │ <div className={tw('bg-blue-600 p-4')}> │ │
│ │ → the tw parses the class │ │
│ │ → the CSS is generated │ │
│ │ → the CSS is injected │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ THE COST │
│ ┌──────────────────────────────────────────────────┐ │
│ │ The bundle: +15 KB (the twind) │ │
│ │ The parse: at the runtime │ │
│ │ The CSP: the 'unsafe-inline' │ │
│ │ The SSR: the mismatch risk │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
The Hybrid
┌────────────────────────────────────────────────────────────┐
│ THE HYBRID │
│ │
│ THE BUILD │
│ ┌──────────────────────────────────────────────────┐ │
│ │ @source inline("bg-{red,blue,green}-{500,600}"); │ │
│ │ → the common classes │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ THE RUNTIME │
│ ┌──────────────────────────────────────────────────┐ │
│ │ const runtimeClasses = serverClasses.filter( │ │
│ │ (cls) => !buildClasses.includes(cls) │ │
│ │ ); │ │
│ │ const runtimeCSS = tw(runtimeClasses.join(' ')); │ │
│ │ → the rare classes │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ The common is the build, the rare is the runtime. │
└────────────────────────────────────────────────────────────┘
Summary
| The item | The value |
|---|---|
| The SDUI | The server-driven UI |
| The allowlist | The safelisted classes |
@source inline | The safelist mechanism |
| The brace expansion | The many classes |
| The server validation | The reject the unknown |
The twind | The runtime Tailwind |
| The hybrid | The both |
| The CSP | The constraint |
| The SSR | The mismatch risk |
| The cache | The build vs the runtime |
Key takeaways:
- The Tailwind build-time engine does not exist at the runtime. The JIT scan is the build step, and the class the server sends must be the class the build produced. The allowlist is the contract.
- The
@source inline(...)forces the class into the build. The safelisted class is in the CSS, and the server may use it. The brace expansion produces the many classes from the one entry. - The server validates the class against the allowlist. The unknown class is rejected, and the client never renders the unstyled element. The validation is the contract.
- The
twindis the runtime Tailwind. It parses the class and injects the CSS at the runtime. The cost is the bundle, the parse, and the CSP. - The CSP is the constraint. The runtime style injection requires the
style-src 'unsafe-inline'or the nonce. The strict CSP is the build-time only. - The SSR must match the client. The runtime generation on the server and the client must produce the same CSS, or the hydration fails.
- The hybrid combines the both. The common classes are the build, and the rare classes are the runtime. The pattern reduces the bundle and the CSP impact.
- The allowlist is the primary. The safe approach is the safelist with the validation. The runtime generation is the fallback for the truly dynamic.
Remember: The server-driven UI and the dynamic runtime styling are the pattern where the server decides the interface. The Tailwind build-time engine does not see the server’s class, and the allowlist, the @source inline, and the runtime generation are the mechanisms. The result is the interface the server can change without the deploy, and the CSS the client already has.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!