Three escape hatches + the production question
<style type="text/tailwindcss"> block in
<head>. When a built-in utility doesn't exist, you reach for
one of three hatches; when the CDN gets slow, you graduate to a real build.
Old v3 added utilities/variants via tailwind.config.js plugins. v4 moved all of that INTO CSS via these directives.
(1) @utility — add a utility that doesn't exist
@utility registers a custom single-purpose class that ships with
every variant (hover:, lg:, …) just like a built-in.
It is the v4 replacement for the v3 "add a plugin" pattern.
tab-4 (custom utility) applied to a <pre>
function f() {
return [ // this line starts with TWO real tab chars
"tab-size:4 makes them 4 spaces wide",
"each red one here is a tab too",
];
}
A complex (nested) custom utility — no-scrollbar hides the scrollbar of the box below (scroll it):
@utility: measuring…
(2) @apply — bundle many utilities into one class
@apply inlines existing utilities into a custom CSS class. Reach for
it sparingly (it hides which utilities compose the class, and can raise
specificity). Best use: third-party overrides, or a handful of repeated clusters.
.card by @apply — so the markup
stays clean while the class still yields to a plain utility
(e.g. an extra rounded-none would override it).
@apply: measuring…
(3) @custom-variant + variant stacking
@custom-variant names your own condition (a data attribute, a media
query, a selector). Variants also stack — leftmost is the outermost wrapper.
[data-theme="midnight"] scope
this span carries the custom variant class midnight:underline
The class only takes effect BECAUSE this box is [data-theme="midnight"].
Move it out and the underline vanishes.
Variant stacking — hover:md:bg-brand
Three conditions composed: hover (outer) wraps md (≥768px)
wraps the bg-brand utility. Resize the window ≥768px and hover:
@custom-variant: measuring…
When to escape the Play CDN — runtime-JIT vs a real build
| path | when | how styles are made | cost |
|---|---|---|---|
Play CDN@tailwindcss/browser@4 |
prototyping, demos, this bundle | compiler runs IN the browser; compiles on load + on DOM change | ships compiler JS; runtime JIT; FOUC; not tree-shaken |
| Real build CLI / @tailwindcss/vite / postcss |
production | compiler runs at BUILD time; scans source, emits static CSS | tiny CSS, zero runtime, purged/tree-shaken, cacheable |
Same directives (@utility, @apply, @custom-variant) work identically under a real build — that is the whole point: prototype on the CDN, ship the build.