Custom utilities and components
Sometimes the ready classes are not enough: you need a CSS property that has no utility, or a repeated set of styles for HTML you do not control. Tailwind 4 gives you three tools: @utility — your own utility, @layer components — a component class, and @apply — utilities inserted into an ordinary CSS rule.
@utility
@utility creates a new utility. It behaves like a built-in one: works with hover:, md: and other variants and is sorted correctly in the CSS. It goes into the main CSS file, for example src/style.css:
@import "tailwindcss";
@utility scrollbar-hidden {
scrollbar-width: none;
}With the browser script it goes inside <style type="text/tailwindcss">:
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<style type="text/tailwindcss">
@utility scrollbar-hidden {
scrollbar-width: none;
}
</style>
<div class="scrollbar-hidden flex w-64 gap-2 overflow-x-auto">
<span class="shrink-0 rounded-full bg-gray-100 px-3 py-1">Headphones</span>
<span class="shrink-0 rounded-full bg-gray-100 px-3 py-1">Speakers</span>
<span class="shrink-0 rounded-full bg-gray-100 px-3 py-1">Chargers</span>
<span class="shrink-0 rounded-full bg-gray-100 px-3 py-1">Cables</span>
</div>The row of categories scrolls sideways, but the scrollbar is hidden.
Components and @apply
@apply inserts the styles of utilities into an ordinary CSS rule. In the components layer this is used for component classes that utilities can still override:
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<style type="text/tailwindcss">
@layer components {
.btn-primary {
@apply rounded-lg bg-indigo-600 px-4 py-2 font-medium text-white hover:bg-indigo-500;
}
}
</style>
<button class="btn-primary">Add to cart</button>
<button class="btn-primary bg-gray-900">Buy now</button>Both buttons are styled with one class. On the second, the bg-gray-900 utility overrides the component's background: Tailwind's utilities layer beats the components layer.
When @apply fits
| Situation | What to use |
|---|---|
| A piece of markup repeats in templates | A template component (lesson in the Practice chapter) |
| HTML is generated by other code: a CMS, Markdown, a widget | @apply or @layer components |
| You need a new CSS property as a class | @utility |
@apply is convenient, but if you move your whole layout into it you are back to plain CSS with invented names — and you lose Tailwind's main advantage. First look for a way to reuse the markup, not the classes.
@apply in component files
In the <style> block of a Vue or Svelte component Tailwind does not know your theme. To make @apply work there, the main CSS file is referenced at the top of the block with @reference "../style.css"; — it gives access to the theme without duplicating the styles.
Common mistake
Making an @apply class for every element:
.card-title {
@apply text-lg font-semibold text-gray-900;
}
.card-price {
@apply text-xl font-bold;
}This is the same CSS with invented names, only longer: to change the heading you now have to find a rule in another file. Keep utilities in the markup and use @apply only for the cases in the table above.