Functions & Directives
A reference for every Tailwind v4 CSS directive and function — @import, @theme, @source, @utility, @variant, @config, --alpha() and --spacing().
Tailwind v4 is configured in CSS, which means the API surface is a small set of at-rules and functions your stylesheet can use. This page documents every one of them: what it does, its exact syntax, and one realistic example.
Overview#
| Directive / function | Purpose |
|---|---|
@import | Pull in Tailwind, or any other stylesheet |
@theme | Define design tokens and generate utilities |
@source | Register extra files for class detection |
@source not | Exclude files from class detection |
@source inline() | Safelist classes that never appear in source |
@utility | Define a custom utility |
@variant | Apply a Tailwind variant inside your own CSS |
@custom-variant | Define a custom variant |
@apply | Inline existing utilities into a rule |
@reference | Make definitions visible to a scoped stylesheet |
@config | Load a legacy tailwind.config.js |
@plugin | Load a JavaScript plugin |
@layer | Place rules in a cascade layer |
--alpha() | Adjust a colour's opacity |
--spacing() | Compute a value from the spacing scale |
--theme() | Read a theme value where var() is not allowed |
theme() | Deprecated Dot-notation theme access |
@import#
Inlines another stylesheet, including Tailwind itself. This is the entry point of every v4 project.
@import "tailwindcss";
@import "./typography.css";@import "tailwindcss";
@import "./components/buttons.css";
@theme {
--color-brand-500: oklch(0.62 0.19 264);
}Import options#
@import "tailwindcss" accepts two modifiers that control source detection:
/* Set the base directory for automatic detection */
@import "tailwindcss" source("../src");
/* Turn automatic detection off entirely */
@import "tailwindcss" source(none);source(none) is worth knowing about for monorepos and libraries where you want to enumerate every scanned path explicitly.
@theme#
Defines design tokens. Every variable in a @theme block becomes both a CSS custom property and a set of utilities, according to its namespace.
@theme { --namespace-name: value; }
@theme inline { … }
@theme static { … }@theme {
--font-display: "Satoshi", sans-serif;
--breakpoint-3xl: 120rem;
--color-avocado-500: oklch(0.84 0.18 117.33);
--ease-snappy: cubic-bezier(0.2, 0, 0, 1);
}| Option | Effect |
|---|---|
| (none) | Utilities reference the variable — background-color: var(--color-avocado-500) |
inline | Utilities inline the variable's value, so runtime changes to a referenced variable take effect |
static | Emit every variable, even ones no utility uses — for reading tokens from JavaScript |
/* A token whose value is set at runtime by next/font */
@theme inline {
--font-sans: var(--font-inter);
}
/* Tokens a charting library reads via getComputedStyle */
@theme static {
--color-chart-1: oklch(0.7 0.15 200);
--color-chart-2: oklch(0.7 0.15 30);
}Full coverage, including namespaces and how to remove defaults, is in Theme & Design Tokens.
@source#
Registers a path for class detection. Tailwind already scans your project automatically — skipping .gitignored paths, node_modules, binaries, CSS files and lockfiles — so @source is for the places that sweep misses.
@source "path";@import "tailwindcss";
/* A design-system package that ships class names in its dist files */
@source "../node_modules/@acmecorp/ui-lib";Paths are resolved relative to the stylesheet the directive appears in.
@source not#
v4.1 Excludes a path from detection. Useful for large directories you know contain no Tailwind classes, or that contain text that looks like classes.
@source not "path";@import "tailwindcss";
@source not "../src/components/legacy";
@source not "../public/vendor";The payoff is build time, and occasionally correctness — a directory of scraped HTML or fixture data can otherwise inflate your output with utilities nothing renders.
@source inline()#
v4.1 Generates classes as if they had been found in a source file. This is v4's replacement for the v3 safelist option.
@source inline("classes");
@source not inline("classes");@import "tailwindcss";
@source inline("underline");Brace expansion generates families of classes without listing each one:
/* The class, plus its hover and focus variants */
@source inline("{hover:,focus:,}underline");
/* Every red shade, plus hover variants: bg-red-50 … bg-red-950 */
@source inline("{hover:,}bg-red-{50,{100..900..100},950}");The {start..end..step} form is a numeric range. {a,b,} is a literal list, and a trailing empty item means "and also the bare class".
@source not inline(…) does the opposite — it prevents classes from being generated even when they do appear in your source:
@source not inline("{hover:,focus:,}bg-red-{50,{100..900..100},950}");@utility#
Defines a custom utility that works with every variant and sorts correctly against the built-ins.
@utility name { … }
@utility name-* { … }@utility content-auto {
content-visibility: auto;
}
@utility tab-* {
tab-size: --value(--tab-size-*, integer, [integer]);
}Functional utilities read their argument with --value(), their slash modifier with --modifier(), and their no-argument fallback with --default(). See Custom Utilities for the full treatment.
@variant#
Applies an existing Tailwind variant to styles you write yourself, so you never have to hand-copy the selector or media query a variant compiles to.
@variant name { … }
@variant a:b { … }
@variant a, b { … }.my-element {
background: white;
@variant dark {
background: black;
}
}v4.3 Variants may be stacked with colons or combined with commas:
.button {
background: var(--color-sky-500);
/* Hovered AND focused */
@variant hover:focus {
background: var(--color-sky-600);
}
}
.link {
color: var(--color-sky-500);
/* Hovered OR focused */
@variant hover, focus {
color: var(--color-sky-600);
}
}@custom-variant#
Registers a new variant name.
@custom-variant name (selector);
@custom-variant name { … @slot; … }/* Shorthand */
@custom-variant theme-midnight (&:where([data-theme="midnight"] *));
/* Block form, for multiple nested rules */
@custom-variant any-hover {
@media (any-hover: hover) {
&:hover {
@slot;
}
}
}@slot marks where the utility's declarations are inserted. See Custom Variants.
@apply#
Inlines existing utility classes into a rule.
.selector { @apply utility utility; }/* Styling a third-party widget's markup */
.select2-dropdown {
@apply rounded-b-lg shadow-md;
}
.select2-search {
@apply rounded border border-gray-300;
}@apply is for markup you cannot edit. It is not a way to build a component layer — see When @apply is the right tool.
@reference#
Makes your theme variables, custom utilities and custom variants visible to a stylesheet that is bundled separately — a Vue/Svelte/Astro <style> block, or a CSS module — without emitting any CSS.
@reference "path";
@reference "tailwindcss";<template>
<h1>Hello world!</h1>
</template>
<style>
@reference "../../app.css";
h1 {
@apply text-2xl font-bold text-red-500;
}
</style>If your theme is unmodified, @reference "tailwindcss"; is enough.
@config#
Compatibility Loads a legacy JavaScript configuration file. v4 does not read tailwind.config.js unless you opt in with this directive.
@config "path/to/tailwind.config.js";@import "tailwindcss";
@config "../../tailwind.config.js";Use it while migrating from v3, or when a dependency's config must keep working. Not everything carries over — corePlugins, safelist and separator are not supported in v4. Use @source inline() in place of safelist.
@plugin#
Compatibility Loads a JavaScript plugin, by package name or by path.
@plugin "name";
@plugin "name" { option: value; }@import "tailwindcss";
@plugin "@tailwindcss/typography";
@plugin "@tailwindcss/forms" {
strategy: "class";
}The optional block passes options to the plugin — the CSS equivalent of the object you would have passed in tailwind.config.js. See Plugins.
@layer#
v4 uses native CSS cascade layers, in this order:
@layer theme, base, components, utilities;Anything not in a layer sits in the implicit layer, which comes last and wins. @layer in v4 is the real CSS at-rule — it no longer registers utilities the way it did in v3.
@layer base { … }
@layer components { … }@import "tailwindcss";
/* Element defaults — every utility can override these */
@layer base {
h1 {
font-size: var(--text-2xl);
font-weight: var(--font-weight-bold);
}
}
/* Opinionated component defaults — utilities still win */
@layer components {
.card {
background-color: var(--color-white);
border-radius: var(--radius-lg);
padding: --spacing(6);
}
}--alpha()#
Adjusts the opacity of a colour. This is the function the /50 opacity modifier uses internally.
--alpha(color / percentage).my-element {
color: --alpha(var(--color-lime-300) / 50%);
}.my-element {
color: color-mix(in oklab, var(--color-lime-300) 50%, transparent);
}<div class="flex flex-wrap gap-3 p-4 text-sm font-medium text-white">
<div class="rounded-lg bg-indigo-600 px-4 py-3">100%</div>
<div class="rounded-lg bg-indigo-600/75 px-4 py-3">75%</div>
<div class="rounded-lg bg-indigo-600/50 px-4 py-3">50%</div>
<div class="rounded-lg bg-indigo-600/25 px-4 py-3 text-indigo-900">25%</div>
</div>In markup the opacity modifier does this for you; --alpha() is for hand-written CSS.
--spacing()#
Multiplies the spacing scale's base unit, so hand-written CSS stays on the same rhythm as your utilities.
--spacing(number).my-element {
margin: --spacing(4);
}.my-element {
margin: calc(var(--spacing) * 4);
}It works inside arbitrary values too:
<div class="py-[calc(--spacing(4)-1px)]">…</div>--theme()#
Reads a theme value in a context where CSS custom properties are not allowed — most importantly, inside a media query condition.
--theme(--variable-name)/* var(--breakpoint-lg) is invalid here; --theme() is resolved at build time */
@media (width >= --theme(--breakpoint-lg)) {
.sidebar {
display: block;
}
}Everywhere else, plain var(--breakpoint-lg) is the right tool — it stays live, and --theme() does not.
The legacy theme() function#
Deprecated Dot-notation access to theme values, carried over from v3.
theme(path.to.value).my-element {
margin: theme(spacing.12);
}It still resolves, so v3 stylesheets keep building. In new code use the CSS variable directly — var(--spacing), var(--color-red-500) — or --spacing() and --theme().
Common mistakes#
- Rules before
@import. Every@importmust be at the top of the file. This is a CSS rule, not a Tailwind one. @layer utilitiesfor custom utilities. Use@utility— the v3 behaviour is gone.@applyin a scoped<style>block with no@reference. The utility names are not in scope, so the build fails.@import "tailwindcss"inside a component. Use@reference.- Expecting
@configto restore everything.corePlugins,safelistandseparatorhave no v4 equivalent through that path. var()inside a media query. CSS does not allow it. Use--theme().- Reaching for
theme()in new code. It is deprecated; theme values are ordinary CSS variables now.
Related topics#
- Theme & Design Tokens —
@themein depth - Custom Utilities —
@utility,@applyand@reference - Custom Variants —
@custom-variantand@variant - Plugins —
@pluginand the official plugin set - v3 → v4 Migration — replacing
@configfor good