Web development
Writing a Tailwind plugin
In Tailwind 4 a custom plugin is an @utility rule in CSS. A JavaScript plugin earns its place only when you ship utilities as a package.
Key points
- @utility adds a custom utility with no JavaScript.
- @plugin loads a JavaScript plugin, from a package or a local file.
- safelist and corePlugins do not exist in Tailwind 4.
In Tailwind CSS 4 a custom plugin is an @utility rule in the CSS; a JavaScript plugin earns its place only when you ship it as a package.
The short path: @utility
@import "tailwindcss";
@utility content-auto {
content-visibility: auto;
contain-intrinsic-size: 1px 500px;
}
The utility lands in the utilities layer and accepts variants: hover:content-auto, lg:content-auto. No JavaScript, no tailwind.config.js, no package.
A utility with a value
A functional utility takes an argument through --value():
@theme {
--tab-size-github: 8;
}
@utility tab-* {
tab-size: --value(--tab-size-*, integer, [integer]);
}
That covers tab-2, tab-76, tab-github and tab-[3]. --value() reads theme keys, bare values and arbitrary values; declarations that do not resolve are left out of the output.
When you need a JavaScript plugin
A JavaScript plugin still earns its place when you publish utilities for other projects. You write it with the plugin() helper:
import plugin from "tailwindcss/plugin";
export default plugin(({ addUtilities, addVariant }) => {
addUtilities({
".content-auto": { "content-visibility": "auto" },
});
addVariant("hocus", ["&:hover", "&:focus-visible"]);
});
You load it with the @plugin directive, which takes a package name or a local path. The stable APIs are addUtilities, addComponents and addVariant.
@import "tailwindcss";
@plugin "./hocus-plugin.js";
The corePlugins and safelist options from the JavaScript config are not supported in 4. In place of a safelist there is @source inline().
The limitation
A JavaScript plugin is your code: it has a name, a version and a test to maintain, and its utilities do not exist in a project until someone loads it. In exchange it is untyped, and a wrong selector fails silently. For two internal utilities @utility costs less and stays in the same file.
Where to continue
The catalogue holds Tailwind components, the tailwind-admin library and hero sections, all built from utilities. The framework is explained in what Tailwind CSS is, missing classes in content paths; to pick a library read the best Tailwind libraries and the comparison Tailwind vs CSS Modules.
Frequently asked questions
Do I still need to write JavaScript plugins in Tailwind 4?
Only if you publish utilities for other projects. Inside one application, @utility in the CSS covers almost every case.
How do you load a plugin in Tailwind 4?
With the @plugin directive followed by the package name or a local path. The directive goes in the CSS file that imports tailwindcss.
What is the difference between @utility and addUtilities?
@utility is a CSS rule that the build processes, addUtilities is a function of the JavaScript API. The first stays in the project, the second needs a package.
Can I use a safelist in Tailwind 4?
No, safelist and corePlugins from the JavaScript config are not supported in 4. In their place is @source inline(), which lists the classes to generate.
Can a plugin add a variant?
Yes, with addVariant in the JavaScript API or with @custom-variant in CSS. For a single variant, @custom-variant is the shorter route.