Tailwind CSS 44 🎨 Custom CSS Directives (@utility and @variant)
The @utility and @variant directives are the two primitives that make Tailwind v4’s CSS-first configuration work. @utility registers a custom utility class with Tailwind’s variant system, so the class supports hover:, md:, dark:, and every other variant. @variant applies a variant inside a CSS rule, replacing the @screen directive from v3 and the hover: prefix in @apply. Together, they let a developer write custom CSS that behaves exactly like the built-in utilities: composable, variant-aware, and purged when unused.
The distinction between the two is the distinction between defining a class and applying a condition. @utility creates a new class. The class name is registered with Tailwind, and the body is the CSS that the class applies. @variant modifies an existing rule by wrapping it in a variant condition. @variant hover { ... } inside a .btn rule compiles to a :hover selector around the declarations. @variant dark { ... } wraps the declarations in the dark mode selector. The directive is the CSS-first equivalent of prefixing a class name with a variant.
The @utility directive replaces the v3 pattern of writing custom utilities inside @layer utilities. In v3, @layer utilities was a Tailwind-specific directive that registered the classes, generated variants, and purged unused styles. In v4, @layer is the native CSS cascade layer, and it does not register classes with Tailwind. A class defined in @layer utilities is plain CSS. The @utility directive is the replacement: it registers the class, generates the variants, and participates in the purge. A custom utility defined with @utility is indistinguishable from a built-in utility in every way that matters.
The @variant directive replaces the @screen directive and the hover: prefix in @apply. In v3, a responsive rule inside a custom class was written with @screen md { ... }, and a hover state was written with @apply hover:bg-blue-600. In v4, both are written with @variant. The directive accepts the same variant syntax as class names, including stacked variants (hover:focus), compound variants (hover, focus for OR conditions), and arbitrary selectors ([&:is(:hover,:focus)]). This makes the CSS-first variant syntax consistent with the class-name syntax.
This chapter covers three areas. First, why these directives exist — the shift from JavaScript configuration to CSS-first configuration and the problem of registering custom classes with Tailwind. Second, how @utility works — static utilities, functional utilities with wildcards, theme values, and the --value() function. Third, how @variant works — basic variants, stacked and compound variants, arbitrary selectors, and the @custom-variant directive for defining new variants. The chapter ends with a complete example session, a quick reference, best practices, common pitfalls, real-world examples, and diagrams showing the directive compilation.
Key point: @utility registers a custom utility with Tailwind’s variant system. @variant applies a variant inside a CSS rule. Both are CSS-first replacements for the v3 @layer utilities and @screen patterns. A custom utility defined with @utility supports every Tailwind variant and is purged when unused. @variant accepts the same stacked and compound syntax as class names, including hover:focus and hover, focus.
Why @utility and @variant exist
The CSS-first configuration problem. Tailwind v4 moved configuration from tailwind.config.js to CSS. The @theme directive defines design tokens. The @utility directive defines custom utilities. The @custom-variant directive defines custom variants. The @variant directive applies variants inside CSS rules. The goal is a single configuration surface: the CSS file. The JavaScript config is still supported via @config, but the recommended path is CSS-first, and the new directives are the primitives that make it possible.
The @layer replacement problem. In v3, a custom utility was written inside @layer utilities, and Tailwind intercepted the layer, registered the class, generated variants, and purged unused styles. In v4, @layer is the native CSS cascade layer. A class inside @layer utilities is plain CSS: it does not support variants, it is not purged, and it cannot be used with @apply. The @utility directive is the replacement. It registers the class with Tailwind, generates the variants, and participates in the purge. The directive is the only way to create a custom class that behaves like a Tailwind utility.
The @screen replacement problem. In v3, a responsive rule inside a custom class was written with @screen md { ... }. The @screen directive was Tailwind-specific, and it was deprecated in v4. The @variant directive is the replacement. @variant md { ... } applies the md breakpoint to the enclosed declarations. The directive accepts any variant, including responsive, state, and custom variants, and it accepts the same stacked and compound syntax as class names.
The compound-variant problem. A rule that applies on hover or focus was written in v3 as two separate rules or with the @screen-style syntax. In v4, @variant hover, focus { ... } applies the declarations when either condition is true. The comma is the OR operator, matching the class-name syntax hover:focus (which is the AND operator) and hover, focus (the OR operator). The @variant directive supports stacked variants (hover:focus), compound variants (hover, focus), and arbitrary selectors ([&:is(:hover,:focus)]), so the CSS-first syntax is as expressive as the class-name syntax.
The custom-variant problem. A project that needs a variant Tailwind does not ship — a md-to-lg range, a theme-coffee class condition, a data attribute selector — defines it with @custom-variant. The directive takes a name and a selector, and the variant is available everywhere: in class names, in @apply, and in @variant. The @custom-variant directive is the registration mechanism; the @variant directive is the application mechanism. A custom variant defined with @custom-variant is used with @variant name { ... } inside a CSS rule.
The trade-off. The CSS-first directives are more explicit than the v3 JavaScript configuration. The custom utility is defined in the same file as the styles that use it, and the variant is applied in the same syntax as the class name. The trade-off is the loss of JavaScript’s expressiveness for complex configurations. A plugin that generates utilities from a data structure is harder to write in CSS than in JavaScript. The @utility directive is for single utilities, not for generating families of utilities from a configuration object. For the common cases, the CSS-first directives are simpler and more readable.
a. The @utility directive
The @utility directive registers a custom utility with Tailwind. The class name is the name after the directive, and the body is the CSS that the class applies. The class supports every Tailwind variant, is purged when unused, and can be used with @apply inside other rules.
Static utilities. A static utility has a fixed name and a fixed body. The class name is used as-is in the markup.
@import "tailwindcss";
@utility content-auto {
content-visibility: auto;
}
@utility scroll-snap-x {
scroll-snap-type: x;
}
The content-auto class is registered with Tailwind. It can be used as class="content-auto", with variants as class="hover:content-auto" or class="md:content-auto", and with @apply inside another rule. It is purged when no markup uses it.
Functional utilities. A functional utility ends with -* in its name, and the * is the placeholder for the value. The --value() function extracts the value from the class name. The utility can accept theme values, bare values, or arbitrary values.
@utility tab-* {
tab-size: --value(integer);
}
The tab-* definition creates classes like tab-2, tab-4, and tab-8. The --value(integer) function extracts the numeric value from the class name and uses it in the tab-size property. The utility generates .tab-2 { tab-size: 2; }, .tab-4 { tab-size: 4; }, and so on. The utility is dynamic: any integer works.
Theme values. The --value() function can resolve values from the theme. --value(--color-*) extracts the value from the class name and looks it up in the --color-* namespace.
@theme {
--color-primary: oklch(65% 0.25 270);
--color-accent: oklch(75% 0.22 320);
}
@utility custom-link-* {
color: --value(--color-*);
}
The custom-link-primary class generates color: var(--color-primary), and custom-link-accent generates color: var(--color-accent). The utility is a dynamic wrapper around the theme’s color variables.
Arbitrary values. The --value() function can accept arbitrary values with brackets. --value([integer]) accepts a bracketed integer, and --value([length]) accepts a bracketed length.
@utility text-stroke-width-* {
-webkit-text-stroke-width: --value([length]);
}
The text-stroke-width-[1px] class generates -webkit-text-stroke-width: 1px. The brackets in the class name signal an arbitrary value, and the --value([length]) function extracts it.
Multiple values with cascade order. The --value() function can be called multiple times in the same utility. The values are tried in order, and the first one that matches is used.
@utility tab-* {
tab-size: --value(--tab-size-*);
tab-size: --value(integer);
tab-size: --value([integer]);
}
The tab-* utility first checks the theme’s --tab-size-* namespace, then a bare integer, then a bracketed integer. This is the cascade order: theme values take precedence over bare values, which take precedence over arbitrary values.
The --modifier() function. The --modifier() function handles the optional /… suffix on a utility. If the modifier is omitted, the declaration that uses it is dropped.
@utility grid-cols-* {
grid-template-columns: repeat(--value(integer), minmax(0, 1fr)) --modifier(--grid-gap-*);
}
The --modifier() function extracts the modifier value from the class name. The utility is functional on both the value and the modifier.
b. The @variant directive
The @variant directive applies a variant inside a CSS rule. The directive wraps the enclosed declarations in the variant’s condition. It replaces the @screen directive from v3 and the hover: prefix in @apply.
Basic variants. A single variant is written as @variant name { ... }.
.btn {
background-color: blue;
@variant hover {
background-color: darkblue;
}
@variant dark {
background-color: navy;
}
}
The hover variant wraps the background-color in a :hover selector. The dark variant wraps the declarations in the dark mode selector. The directives compile to the same CSS as the hover: and dark: prefixes in class names.
Responsive variants. The @screen directive is replaced by @variant.
.card {
padding: 1rem;
@variant md {
padding: 2rem;
}
@variant lg {
padding: 3rem;
}
}
The @variant md directive applies the md breakpoint to the enclosed declarations. The @variant lg directive applies the lg breakpoint. The directives compile to the same media queries as the md: and lg: prefixes.
Stacked variants. The @variant directive accepts the colon-stacked syntax for AND conditions.
.btn {
@variant hover:focus {
outline: 2px solid currentColor;
}
}
The hover:focus condition requires both the hover state and the focus state to be true. The directive compiles to the combined selector.
Compound variants. The @variant directive accepts the comma syntax for OR conditions.
.btn {
@variant hover, focus {
border: 4px solid black;
}
}
The hover, focus condition requires either the hover state or the focus state. The directive compiles to the combined selector.
Arbitrary selectors. The @variant directive accepts arbitrary selectors in brackets.
.btn {
@variant [&:is(:hover,:focus)] {
background-color: lightblue;
}
}
The [&:is(:hover,:focus)] selector is applied directly. The & is the current element. The directive compiles to the selector with the declarations.
The @custom-variant directive. A custom variant is defined with @custom-variant. The directive takes a name and a selector.
@custom-variant md-to-lg (@media (width >= 768px) and (width <= 1024px));
@custom-variant theme-coffee (&:where(.coffee, .coffee *));
The md-to-lg variant is a media query range. The theme-coffee variant applies when the element or an ancestor has the .coffee class. Both are used with @variant inside a CSS rule and with the name: prefix in class names.
.my-element {
@variant md-to-lg {
width: 1000px;
}
@variant theme-coffee {
background-color: brown;
}
}
The custom variant is available everywhere: in class names (md-to-lg:w-[1000px]), in @apply, and in @variant.
c. Combining the two directives
The two directives are designed to work together. A custom utility can use @variant to apply variants inside its definition, and a @variant rule can use @apply to apply a custom utility.
Variants inside a custom utility. A utility defined with @utility can use @variant to include state variants in its definition.
@utility btn {
padding: calc(var(--spacing) * 2) calc(var(--spacing) * 4);
border-radius: var(--radius-md);
background-color: var(--color-primary-600);
@variant hover {
background-color: var(--color-primary-700);
}
@variant focus-visible {
outline: 2px solid var(--color-primary-400);
outline-offset: 2px;
}
}
The btn utility includes the hover and focus-visible states. The states are part of the utility’s definition, and the markup uses class="btn" without the hover: or focus-visible: prefixes. The utility is a single class with its states built in.
Applying a custom utility inside a variant. A @variant rule can use @apply to apply a custom utility.
@custom-variant theme-coffee (&:where(.coffee, .coffee *));
.my-component {
@variant theme-coffee {
@apply btn;
}
}
The theme-coffee variant applies the btn utility when the coffee theme is active. The @apply directive resolves the btn class from the @utility definition.
The composition is the point. A custom utility is a named set of styles. A variant is a condition. The two directives let a developer compose them: a utility can contain variants, and a variant can contain utilities. The result is a CSS-first system where custom styles are as composable as the built-in utilities.
Complete Example Session
/* ============================================
PART 1: A STATIC UTILITY
============================================ */
@import "tailwindcss";
@utility content-auto {
content-visibility: auto;
}
/* <div class="content-auto"> ... </div>
<div class="hover:content-auto"> ... </div>
<div class="md:content-auto"> ... </div> */
/* ============================================
PART 2: A FUNCTIONAL UTILITY
============================================ */
@utility tab-* {
tab-size: --value(integer);
}
/* <pre class="tab-2"> ... </pre> → tab-size: 2;
<pre class="tab-8"> ... </pre> → tab-size: 8; */
/* ============================================
PART 3: A THEME-VALUE UTILITY
============================================ */
@theme {
--color-primary: oklch(65% 0.25 270);
--color-accent: oklch(75% 0.22 320);
}
@utility custom-link-* {
color: --value(--color-*);
}
/* <a class="custom-link-primary"> ... </a>
→ color: var(--color-primary); */
/* ============================================
PART 4: A UTILITY WITH A VARIANT
============================================ */
@utility btn {
padding: calc(var(--spacing) * 2) calc(var(--spacing) * 4);
border-radius: var(--radius-md);
background-color: var(--color-primary-600);
@variant hover {
background-color: var(--color-primary-700);
}
}
/* <button class="btn"> ... </button>
The hover state is built into the utility. */
/* ============================================
PART 5: A BASIC @variant
============================================ */
.card {
padding: 1rem;
@variant md {
padding: 2rem;
}
@variant dark {
background-color: var(--color-gray-900);
}
}
/* Replaces the @screen directive from v3. */
/* ============================================
PART 6: A STACKED @variant
============================================ */
.btn {
@variant hover:focus {
outline: 2px solid currentColor;
}
}
/* Both hover and focus must be true. */
/* ============================================
PART 7: A COMPOUND @variant
============================================ */
.btn {
@variant hover, focus {
border: 4px solid black;
}
}
/* Either hover or focus. */
/* ============================================
PART 8: AN ARBITRARY @variant
============================================ */
.btn {
@variant [&:is(:hover,:focus)] {
background-color: lightblue;
}
}
/* The selector is applied directly. */
/* ============================================
PART 9: A CUSTOM VARIANT
============================================ */
@custom-variant md-to-lg (@media (width >= 768px) and (width <= 1024px));
@custom-variant theme-coffee (&:where(.coffee, .coffee *));
.my-element {
@variant md-to-lg {
width: 1000px;
}
@variant theme-coffee {
background-color: brown;
}
}
/* The custom variants are available in class names
and in @variant. */
/* ============================================
PART 10: THE COMPLETE STYLESHEET
============================================ */
@import "tailwindcss";
@theme {
--color-primary-600: oklch(0.55 0.2 250);
--color-primary-700: oklch(0.48 0.2 250);
}
@custom-variant theme-coffee (&:where(.coffee, .coffee *));
@utility content-auto {
content-visibility: auto;
}
@utility btn {
padding: calc(var(--spacing) * 2) calc(var(--spacing) * 4);
border-radius: var(--radius-md);
background-color: var(--color-primary-600);
@variant hover {
background-color: var(--color-primary-700);
}
@variant theme-coffee {
background-color: brown;
}
}
@utility custom-link-* {
color: --value(--color-*);
}
/* The complete stylesheet: @theme, @custom-variant,
@utility, and @variant in one file. */
The ten parts show a static utility, a functional utility, a theme-value utility, a utility with a variant, a basic @variant, a stacked @variant, a compound @variant, an arbitrary @variant, a custom variant, and the complete stylesheet.
Quick Reference
@utility Syntax
| Form | Meaning |
|---|---|
@utility name { ... } | Static utility |
@utility name-* { ... } | Functional utility |
--value(integer) | Extract a bare integer |
--value(--color-*) | Extract a theme value |
--value([length]) | Extract an arbitrary value |
--modifier(--spacing-*) | Handle the /… suffix |
@variant Syntax
| Form | Meaning |
|---|---|
@variant hover { ... } | Single variant |
@variant md { ... } | Responsive variant |
@variant hover:focus { ... } | Stacked (AND) |
@variant hover, focus { ... } | Compound (OR) |
@variant [&:is(:hover,:focus)] { ... } | Arbitrary selector |
@custom-variant Syntax
| Form | Meaning |
|---|---|
@custom-variant name (selector); | Define a custom variant |
@custom-variant md-to-lg (@media (...)); | Media query variant |
@custom-variant theme-coffee (&:where(.coffee, .coffee *)); | Class-based variant |
v3 vs v4
| v3 | v4 |
|---|---|
@layer utilities { .class { ... } } | @utility class { ... } |
@screen md { ... } | @variant md { ... } |
@apply hover:bg-blue-600 | @variant hover { @apply bg-blue-600 } |
tailwind.config.js variants | @custom-variant |
Best Practices
✅ Do This:
/* Use @utility for custom utilities */
@utility content-auto { content-visibility: auto; } // ✅
/* Use --value() for functional utilities */
@utility tab-* { tab-size: --value(integer); } // ✅
/* Use @variant inside @utility for state variants */
@utility btn { @variant hover { ... } } // ✅
/* Use @variant for responsive rules */
.card { @variant md { padding: 2rem; } } // ✅
/* Use @custom-variant for project-specific variants */
@custom-variant theme-coffee (&:where(.coffee, .coffee *)); // ✅
/* Use stacked and compound syntax */
@variant hover:focus { ... } @variant hover, focus { ... } // ✅
❌ Don’t Do This:
/* Don't use @layer utilities for custom utilities in v4 */
@layer utilities { .content-auto { ... } } // not registered // ❌
/* Don't use @screen in v4 */
@screen md { ... } // deprecated // ❌
/* Don't use hover: prefix in @apply for state variants */
@apply hover:bg-blue-600; // use @variant hover instead // ❌
/* Don't forget the wildcard for functional utilities */
@utility tab { tab-size: --value(integer); } // no -* // ❌
/* Don't use @variant without a reference */
/* Add @reference "tailwindcss"; in isolated CSS files */ // ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Utility not registered | Used @layer instead of @utility | Use @utility |
| Functional utility doesn’t generate | Missing -* in the name | Add -* |
@variant not applied | No reference to Tailwind | Add @reference |
| Custom variant not found | Defined after use | Define before use |
@apply fails | Class not registered | Use @utility |
| Theme value not resolved | Wrong --value() namespace | Check the theme variable |
| Variant syntax error | Missing colon or comma | Match the class-name syntax |
Real-World Examples
1. Static Utility
@utility content-auto { content-visibility: auto; }
2. Functional Utility
@utility tab-* { tab-size: --value(integer); }
3. Theme-Value Utility
@utility custom-link-* { color: --value(--color-*); }
4. Utility with Variant
@utility btn { @variant hover { background-color: var(--color-primary-700); } }
5. Responsive Variant
.card { @variant md { padding: 2rem; } }
6. Stacked Variant
@variant hover:focus { outline: 2px solid currentColor; }
7. Compound Variant
@variant hover, focus { border: 4px solid black; }
8. Custom Variant
@custom-variant md-to-lg (@media (width >= 768px) and (width <= 1024px));
9. Arbitrary Variant
@variant [&:is(:hover,:focus)] { background-color: lightblue; }
10. Complete Utility with States
@utility btn {
padding: calc(var(--spacing) * 2) calc(var(--spacing) * 4);
@variant hover { background-color: var(--color-primary-700); }
}
Visual
The @utility Compilation
┌──────────────────────────────────────────────────────────────┐
│ THE @utility COMPILATION │
│ │
│ Source: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ @utility btn { │ │
│ │ padding: 0.5rem 1rem; │ │
│ │ @variant hover { │ │
│ │ background-color: darkblue; │ │
│ │ } │ │
│ │ } │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Compiled: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ .btn { │ │
│ │ padding: 0.5rem 1rem; │ │
│ │ } │ │
│ │ .btn:hover { │ │
│ │ background-color: darkblue; │ │
│ │ } │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ The utility is registered with Tailwind.
│ Variants are supported: hover:btn, md:btn.
│ The class is purged when unused.
│ │
└──────────────────────────────────────────────────────────────┘
The @variant Compilation
┌──────────────────────────────────────────────────────────────┐
│ THE @variant COMPILATION │
│ │
│ Source: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ .card { │ │
│ │ padding: 1rem; │ │
│ │ │ │
│ │ @variant md { │ │
│ │ padding: 2rem; │ │
│ │ } │ │
│ │ │ │
│ │ @variant hover, focus { │ │
│ │ border: 4px solid black; │ │
│ │ } │ │
│ │ } │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Compiled: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ .card { padding: 1rem; } │ │
│ │ @media (min-width: 768px) { │ │
│ │ .card { padding: 2rem; } │ │
│ │ } │ │
│ │ .card:hover, .card:focus { │ │
│ │ border: 4px solid black; │ │
│ │ } │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ @variant replaces @screen and the hover: prefix.
│ Stacked (hover:focus) and compound (hover, focus)
│ syntax match the class-name syntax.
│ │
└──────────────────────────────────────────────────────────────┘
v3 vs v4 Directives
┌──────────────────────────────────────────────────────────────┐
│ v3 vs v4 DIRECTIVES │
│ │
│ v3: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ @layer utilities { │ │
│ │ .content-auto { content-visibility: auto; } │ │
│ │ } │ │
│ │ │ │
│ │ @screen md { .card { padding: 2rem; } } │ │
│ │ │ │
│ │ .btn { @apply hover:bg-blue-600; } │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ v4: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ @utility content-auto { │ │
│ │ content-visibility: auto; │ │
│ │ } │ │
│ │ │ │
│ │ .card { @variant md { padding: 2rem; } } │ │
│ │ │ │
│ │ .btn { @variant hover { @apply bg-blue-600; } } │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ The v4 directives are CSS-first, variant-aware, and
│ consistent with the class-name syntax.
│ │
└──────────────────────────────────────────────────────────────┘
Custom Variants
┌──────────────────────────────────────────────────────────────┐
│ CUSTOM VARIANTS │
│ │
│ @custom-variant md-to-lg (@media (width >= 768px) and (width <= 1024px));
│ @custom-variant theme-coffee (&:where(.coffee, .coffee *));
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Usage in class names: │ │
│ │ class="md-to-lg:w-[1000px]" │ │
│ │ class="theme-coffee:bg-brown" │ │
│ │ │ │
│ │ Usage in @variant: │ │
│ │ .my-element { │ │
│ │ @variant md-to-lg { width: 1000px; } │ │
│ │ @variant theme-coffee { background: brown; } │ │
│ │ } │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ The custom variant is registered once and used everywhere. │
│ │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
@utility | Registers a custom utility with Tailwind |
| Static utility | @utility name { ... } |
| Functional utility | @utility name-* { ... } |
--value(integer) | Extract a bare integer |
--value(--color-*) | Extract a theme value |
--value([length]) | Extract an arbitrary value |
@variant | Applies a variant inside a CSS rule |
| Stacked variant | @variant hover:focus { ... } |
| Compound variant | @variant hover, focus { ... } |
@custom-variant | Defines a new variant |
Key takeaways:
@utilityregisters a custom utility with Tailwind. The class supports every variant, is purged when unused, and can be used with@apply. It replaces the v3@layer utilitiespattern.- Static utilities have a fixed name and body.
@utility content-auto { ... }creates a single class. Functional utilities end with-*and use--value()to extract the value from the class name. --value()resolves theme values, bare values, and arbitrary values.--value(--color-*)looks up the theme namespace,--value(integer)accepts a bare integer, and--value([length])accepts a bracketed value. The values are tried in cascade order.@variantapplies a variant inside a CSS rule. It replaces the@screendirective and thehover:prefix in@apply. The directive accepts the same stacked (hover:focus) and compound (hover, focus) syntax as class names.@custom-variantdefines a new variant. The directive takes a name and a selector, and the variant is available in class names,@apply, and@variant. This is how a project defines a variant Tailwind does not ship.- The two directives compose. A custom utility can use
@variantfor its states, and a@variantrule can use@applyto apply a custom utility. The result is a CSS-first system where custom styles are as composable as the built-in utilities. - The v4 directives are CSS-first replacements for v3 patterns.
@utilityreplaces@layer utilities, and@variantreplaces@screen. The new directives are variant-aware, consistent with the class-name syntax, and registered with Tailwind. - The directives are registered with Tailwind and participate in the purge. A class defined with
@utilityis treated like a built-in utility: it supports variants, is purged when unused, and works with@apply. A class defined in@layeris plain CSS.
Remember: @utility and @variant are the two primitives of Tailwind v4’s CSS-first configuration. @utility registers a custom utility with Tailwind’s variant system, so the class supports hover:, md:, dark:, and every other variant. @variant applies a variant inside a CSS rule, replacing the @screen directive and the hover: prefix in @apply. The directives are consistent with the class-name syntax: stacked variants use colons, compound variants use commas, and arbitrary selectors use brackets. @custom-variant defines new variants that are available everywhere. Together, they let a developer write custom CSS that behaves exactly like the built-in utilities: composable, variant-aware, and purged when unused.
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!