Directives
Directives are the @-prefixed at-rules you write in your input CSS to compose, extend, and order Tailwind's output. This page covers @apply, @utility, @variant, @custom-variant, and @layer. @theme is covered in Theme; @plugin in Plugins; and @source in @source.
@apply
@apply inlines the declarations of existing utilities into your own selector. Use it to attach utility styles to a semantic class, including inside nested selectors and media queries.
use TailwindPHP\tw;
$css = tw::generate('<div class="btn">', '
@import "tailwindcss";
@theme { --color-brand: #3b82f6; }
.btn {
@apply px-4 py-2 rounded-lg bg-brand text-white;
}
');
Variants work in an @apply list — @apply bg-blue-500 hover:bg-blue-600; expands the :hover state, and responsive prefixes such as @apply px-4 md:px-6 lg:px-8; emit the corresponding @media rules. The important modifier is honored too (@apply !flex;). Nested selectors are supported, so a rule like .card { h2 { @apply text-xl font-bold; } } applies utilities to the nested h2.
@apply is a statement: @apply px-4 { … } with a body throws. An @apply that lists only dashed idents (@apply --my-mixin;) is a CSS mixin from the CSS mixins draft, not a utility list, so TailwindPHP does not resolve it; putting a mixin and utilities in the same @apply throws (You cannot use @apply with both mixins and utilities…). Mixin rules are currently dropped from the output rather than passed through (#12).
@utility
@utility registers a custom utility that participates in Tailwind's engine — it sorts into the utilities layer and accepts variants like any built-in. A static utility takes a fixed body:
@utility content-auto {
content-visibility: auto;
}
Because it is a real utility, variants compose automatically (hover:content-auto, md:content-auto). The body may itself use @apply and nested selectors:
@utility btn-primary {
@apply px-4 py-2 bg-blue-500 text-white rounded;
}
@utility card {
color: red;
&:hover { color: blue; }
}
A functional utility uses a -* suffix in the name and reads the matched value with --value():
@theme {
--gutter-wide: 3rem;
}
@utility gutter-* {
padding-inline: --value(--gutter-*, [length]);
padding-inline: --spacing(--value(integer));
}
--value() resolves the candidate's value against each source in turn: a theme namespace (--gutter-*), an arbitrary value with a data type ([length]), or a bare value type (integer, number, percentage, ratio). A declaration whose --value() does not resolve is dropped, so stacking declarations gives a fallback chain:
| Class | Output |
|---|---|
gutter-wide |
padding-inline: var(--gutter-wide); |
gutter-[5px] |
padding-inline: 5px; |
gutter-4 |
padding-inline: calc(var(--spacing) * 4); |
--modifier() resolves the part after a / the same way: with line-height: --modifier(integer); in the body, gutter-4/7 also emits line-height: 7. Utility names follow the TailwindCSS v4.3.3 rules: at most one /, % only at the end after a digit, and dots only between digits.
@variant
@variant applies a variant to declarations inside your own CSS, without defining a utility:
.card {
color: red;
@variant hover {
color: blue;
}
@variant dark {
color: white;
}
}
.card {
color: red;
}
@media (hover: hover) {
.card:hover {
color: #00f;
}
}
@media (prefers-color-scheme: dark) {
.card {
color: #fff;
}
}
Variants can be stacked (@variant hover:focus), listed with commas, nested inside each other, and can name your own @custom-variants. A top-level @variant without a body, or with @slot, is treated as @custom-variant for backwards compatibility.
@custom-variant
@custom-variant defines a new variant you can prefix onto any utility. The compact form names the variant and gives a selector or at-rule in parentheses.
A selector-list variant uses & for the element:
@custom-variant hocus (&:hover, &:focus);
hocus:bg-blue-500 then emits both the :hover and :focus rules. An attribute form works the same way:
@custom-variant loading (&[data-loading="true"]);
An at-rule variant wraps matched utilities in that at-rule:
@custom-variant tablet (@media (min-width: 600px));
Custom variants stack with built-ins — sm:hocus:bg-blue-500 nests the hocus selector inside the sm media query. For more involved cases, use the block form with @slot marking where the utility's declarations are injected:
@custom-variant child {
& > * {
@slot;
}
}
@custom-variant sidebar-open {
.sidebar-open & {
@slot;
}
}
@layer
@layer controls cascade order using native CSS cascade layers. A bare @layer statement declares the order of layers; later layers win regardless of selector specificity. Tailwind's canonical order is:
@layer theme, base, components, utilities;
This is exactly what @import "tailwindcss" sets up internally. You can declare your own order, or split it across multiple statements (@layer reset, base; then @layer components, utilities;), and assign rules to a named layer with a block:
use TailwindPHP\tw;
$css = tw::generate('<article><h1>Title</h1></article>', '
@import "tailwindcss";
@layer base {
h1 { font-size: var(--text-2xl); }
a { color: var(--color-blue-600); text-decoration-line: underline; }
}
');
Adding base styles via @layer base keeps them below components and utilities, so a utility class always overrides your element defaults. The same applies to @layer components { … } for component classes that should sit beneath utilities. Anonymous layers and layer() modifiers on @import are covered in Imports.