plugins & directives — @plugin / @reference / @variant

[check: …]
📖 Pair this live demo with the companion guide (.md) — this page is the rendered ground truth for all three wiring directives. ↗ These at-rules are the bridge between the CDN and a real build — see @source & content detection (how Tailwind finds your classes) and build tooling (CLI / Vite / PostCSS, where @plugin actually resolves).

the three wiring directives — at a glance

v4 is configured in CSS, not JS. These three at-rules are how you wire Tailwind into a real build pipeline (CLI / Vite / PostCSS). They replace v3's tailwind.config.js knobs: plugins:[], scoped-style hacks, and addVariant(). None of them produce classes by themselves — they extend what Tailwind knows or emits.

directiveone-line jobreplaces (v3)runs in CDN?
@plugin load a JS plugin (official or 3rd-party) and optionally pass it options plugins: ["@tailwindcss/typography"] in config no — needs npm resolution
@reference import theme + utilities into a scoped style (Vue/Svelte/CSS modules) for @apply without duplicating output no equivalent — v3 needed workarounds / @layer hacks no — needs a build graph
@variant apply a variant from inside your own CSS (hover, dark, custom) leaving CSS for a hover: utility class YES — see Panel 5

1 · @plugin — load official & custom plugins simulated

The Play CDN cannot resolve npm packages, so this panel is a faithful simulation of what a real build (npm i -D @tailwindcss/typography then @plugin "@tailwindcss/typography";) produces. The source on the left is what you write; the table lists the official plugins and what each ships.

@import "tailwindcss"; /* Official plugins — add ONE line each */ @plugin "@tailwindcss/typography"; /* adds .prose + prose-{size} */ @plugin "@tailwindcss/forms"; /* opinionated form resets */ @plugin "@tailwindcss/aspect-ratio"; /* aspect-w / aspect-h (legacy)*/ /* Pass options to a plugin via a brace block */ @plugin "@tailwindcss/typography" { className: "wysiwyg"; /* .prose → .wysiwyg everywhere */ } /* Custom (local file or npm) plugin */ @plugin "./plugins/my-plugin.js";
official plugininstallsv4 status
@tailwindcss/typography .prose, prose-sm…prose-xl, prose-invert, prose-{color} — opinionated long-form text styling JS plugin — load via @plugin
@tailwindcss/forms resets + consistent styling for input, select, checkbox, radio across browsers JS plugin — load via @plugin
@tailwindcss/aspect-ratio aspect-w-* / aspect-h-* (pre-aspect-ratio CSS shim) largely obsolete — use built-in aspect-*
@tailwindcss/line-clamp line-clamp-* built into v4 core — do NOT install
@tailwindcss/container-queries @container + @sm:@7xl: variants built into v4 core — do NOT install

💡 Custom plugins are JS files exporting ({ addUtilities, addVariant, theme, … }) => { … } — the v3 API still works under @plugin. But for new, static behavior prefer the CSS-first directives (@utility, @custom-variant, @theme) which need no JS and work everywhere. See @utility.

2 · what @plugin "@tailwindcss/typography" actually buys you simulated

Same HTML on both sides. Left = raw browser defaults. Right = the .prose class (here simulated by fake-prose, a hand-built @utility). The real plugin also does code-block styling, table borders, quote marks, and per-element color theming — but the rhythm + heading scale + link treatment below is the headline value.

raw HTML (no prose)

Shipping faster

The deploy loop

Most teams think deploys are about CI speed. They are really about feedback.

  • push small
  • ship often

with fake-prose (≈ .prose)

Shipping faster

The deploy loop

Most teams think deploys are about CI speed. They are really about feedback.

  • push small
  • ship often

The "after" side is rendered by the @utility fake-prose + nested selectors in this page's <style type="text/tailwindcss">. That is a CSS-first, CDN-runnable stand-in; the real prose class comes from @plugin "@tailwindcss/typography" in a build.

3 · @reference — make @apply work inside Vue/Svelte scoped styles build-only

Component frameworks (Vue SFCs, Svelte, CSS modules) compile each <style> in isolation. If you write @apply text-cyan-400 there, the compiler has no idea what text-cyan-400 is — it lives in your root Tailwind import. @reference imports the theme + utilities for resolution only: the symbols become visible, but Tailwind's CSS is not emitted again (no duplication across every component file).

<!-- ProfileCard.vue --> <template><h1 class="title">Hello</h1></template> <style scoped> /* WITHOUT this line: "@apply text-cyan-400" → unknown utility error */ @reference "tailwindcss"; /* Use the DEFAULT theme. If you customized @theme/@custom-variant/@plugin, point at your real entry instead: @reference "../../app.css"; */ .title { @apply text-2xl font-bold text-cyan-400; } </style>
you writewhen to use it
@reference "tailwindcss"; scoped style that only needs the default theme (no @theme/@plugin/@custom-variant of your own)
@reference "../../app.css"; scoped style that needs your tokens/variants — point at the file that does @import "tailwindcss" + your customizations
@reference "#app.css"; subpath import via package.json "imports" map (CLI / Vite / PostCSS only)

⚠️ @reference is not @import. @import "tailwindcss" emits Tailwind's full CSS wherever it appears (you'd duplicate it per component). @reference emits nothing — it only teaches the local compiler about the symbols.

4 · @variant — apply a variant from inside your CSS live

Instead of leaving your stylesheet for a hover: / dark: utility, use @variant inline. The button below is styled by ONE rule — .demo-btn — defined in this page's <style type="text/tailwindcss"> block. Its hover and a custom pressed (= :active) variant are emitted by the CDN right now. Hover then click it.

@custom-variant pressed (&:active); .demo-btn { background-color: var(--color-cyan-600); color: white; /* …base styles… */ @variant hover { background-color: var(--color-cyan-400); } @variant pressed { transform: scale(0.97); } }

base:

hover:

@variant accepts any variant — built-in (hover, focus, dark, md) or one you registered with @custom-variant (here: pressed). It's the in-CSS twin of the variant: class prefix.

intent → directive

intentdirectiveexample
use the typography plugin's .prose @plugin @plugin "@tailwindcss/typography";
rename .prose to a custom class @plugin { … } @plugin "@tailwindcss/typography" { className: "wysiwyg"; }
@apply inside a Vue <style scoped> @reference @reference "tailwindcss"; (default) or @reference "../../app.css"; (your tokens)
apply hover from inside CSS @variant .x { color:black; @variant hover { color:white; } }
apply a custom variant from inside CSS @custom-variant + @variant @custom-variant pressed (&:active); then @variant pressed { … }
add a brand-new static utility (no JS) @utility @utility tab-4 { tab-size: 4; } — see functional utility