When a pattern repeats across a codebase, a first-class utility or variant keeps markup clean and consistent.
Tailwind v4 lets you add utilities and variants that behave exactly like the built-in ones: they work with every variant, sort correctly and land in the utilities layer.
Static utilities with @utility:
@utility content-auto { content-visibility: auto; }Now content-auto, md:content-auto and hover:content-auto all work.
Functional utilities end in -* and read the value with --value():
@utility tab-* { tab-size: --value(integer); }makes tab-2, tab-4 and so on. --value(--tab-size-*) reads from a theme namespace instead, and --value([integer]) accepts arbitrary values like tab-[7]. Several --value() forms can be combined as fallbacks in one declaration.
Custom variants with @custom-variant:
@custom-variant theme-midnight (&:where([data-theme=midnight] *));lets you write theme-midnight:bg-black. The block form accepts @slot for complex cases, such as wrapping in a media query.
Using variants in your own CSS: inside a regular rule, @variant dark { ... } applies Tailwind's dark variant to those declarations.
Plugins: @plugin "@tailwindcss/typography"; loads a JavaScript plugin. The official typography plugin provides prose for rich text, and the forms plugin resets form controls. A legacy v3 config can still be loaded with @config "./tailwind.config.js";.
Example
/* app.css */
@import "tailwindcss";
@plugin "@tailwindcss/typography";
@utility scrollbar-hidden {
scrollbar-width: none;
&::-webkit-scrollbar { display: none; }
}
@theme { --tab-size-github: 8; }
@utility tab-* {
tab-size: --value(--tab-size-*, integer, [integer]); /* theme, number, or [n] */
}
@custom-variant theme-midnight (&:where([data-theme=midnight], [data-theme=midnight] *));
.legacy-widget {
background: white;
@variant dark { background: black; } /* reuse Tailwind's dark variant */
}
/* Markup: <pre class="tab-4 md:tab-github scrollbar-hidden">...</pre>
<article class="prose theme-midnight:prose-invert">...</article> */Remember
- @utility adds utilities that work with all variants
- Functional utilities use name-* and --value()
- @custom-variant defines new variants; @variant uses one inside CSS
- @plugin loads JS plugins such as typography
Watch out for
- Writing custom utilities as plain classes in @layer utilities, where v4 no longer treats them as variant-aware utilities; use @utility.