@apply bundles existing utilities into a class. Sometimes you need the opposite: a new utility Tailwind does not ship, a variant for a state it does not know, or a default style for every <h1>. v4 provides three directives for this. After this lesson you will be able to write utilities that support every variant, define your own variants, and set base styles without fighting the cascade.
In v4, a class inside @layer utilities is plain CSS and ignores hover: and md:. To register a real utility, use @utility:
@utility content-auto {
content-visibility: auto;
}
@utility scrollbar-hidden {
scrollbar-width: none;
&::-webkit-scrollbar { display: none; }
}Both now behave like built-ins: md:content-auto and hover:scrollbar-hidden work, IntelliSense suggests them, and they are emitted only when used. Nesting with & and @apply both work inside @utility.
A * in the name makes the utility accept a value. The --value() function decides which values are allowed:
@theme {
--tab-size-github: 8;
}
@utility tab-* {
tab-size: --value(--tab-size-*); /* tab-github, from the theme */
tab-size: --value(integer); /* tab-2, tab-4: bare numbers */
tab-size: --value([integer]); /* tab-[13]: arbitrary values */
}Tailwind keeps whichever declaration resolves, so several sources form a fallback chain. Other forms include --value(percentage), --spacing(--value(number)) to reuse the spacing scale, and --modifier() to read a slash modifier. Negative versions are declared separately as @utility -inset-*.
@custom-variant registers a new prefix. The shorthand form takes a selector where & is the element:
@custom-variant theme-midnight (&:where([data-theme="midnight"], [data-theme="midnight"] *));
@custom-variant hocus (&:hover, &:focus-visible);theme-midnight:bg-slate-950 and hocus:underline are now valid everywhere. The same directive is how class-based dark mode is enabled: @custom-variant dark (&:where(.dark, .dark *));.
The long form with @slot supports at-rules and multiple selectors:
@custom-variant any-hover {
@media (any-hover: hover) {
&:hover { @slot; }
}
}@slot marks where the utility's declarations go; use this form for media and supports queries.
When writing ordinary CSS rules, you can still use variants instead of repeating media queries and selectors:
.prose-callout {
background: var(--color-amber-50);
@variant dark { background: var(--color-amber-950); }
@variant md { padding: --spacing(6); }
}@variant compiles to the same selector or media query the utility would use, so behaviour stays consistent.
Tailwind's output is organised in cascade layers: theme, base, components, utilities. Placing your own rules in base or components guarantees that utilities still win when both apply:
@layer base {
body { @apply bg-white text-gray-900 antialiased; }
a { @apply text-blue-700 underline-offset-2; }
}
@layer components {
.card { @apply rounded-xl border border-gray-200 bg-white p-6 shadow-sm; }
}Because .card lives in the components layer, <div class="card p-8"> gets the larger padding — utilities come later. Preflight (the reset) is part of base; to skip it, import the layers individually without preflight.css:
@layer theme, base, components, utilities;
@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/utilities.css" layer(utilities);.my-class in @layer utilities and expecting variants to work; use @utility.*, so tab-4 is never generated.What is the difference between a class in `@layer utilities` and one declared with `@utility` in v4?
@utility name { … } registers a real utility with variant and IntelliSense support; @layer utilities no longer does.@utility name-* { prop: --value(…); } creates functional utilities that read theme keys, bare values or arbitrary values.@custom-variant adds new prefixes, using a selector shorthand or @slot for at-rules.@variant applies existing variants inside handwritten CSS rules.@layer base / @layer components so utilities keep priority; import layers manually to drop Preflight.Next lesson: Official Plugins: Typography and Forms — add @tailwindcss/typography for rich text and @tailwindcss/forms for consistent form controls.