Reusing Styles and @apply
Extracting repeated utility combinations into components instead of leaning on @apply, and when @apply is still worth it.
2 phút đọc
A fair question once you've written a few components: if the same button classes show up on ten different buttons, isn't that just as repetitive as the CSS Tailwind was supposed to replace? The answer is to extract a component, not to fight the utility classes themselves.
Extract a component, not a CSS class
<!-- Repeated everywhere -->
<button class="rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700">
Save
</button>
<button class="rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700">
Continue
</button>// Button.jsx
function Button({ children, ...props }) {
return (
<button
className="rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700"
{...props}
>
{children}
</button>
);
}Now the class list is written once, inside a real component, and every usage becomes <Button>Save</Button>. This is the idiomatic fix in any component-based framework (React, Vue, Svelte) — the repetition lived in markup, so the fix is a markup-level abstraction, not a CSS-level one.
What @apply does
Tailwind still provides @apply, which lets you pull utility classes into a custom CSS class:
.btn-primary {
@apply rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700;
}<button class="btn-primary">Save</button>This looks like it solves the same problem, and technically it does — but it quietly reintroduces the thing utility-first CSS was designed to avoid: a named class in a separate file that you now have to context-switch to in order to understand what btn-primary actually looks like. You've traded "read the markup, see the styles" for "read the markup, then go find btn-primary in a stylesheet."
Why component extraction usually wins
- No new naming problem.
<Button variant="primary">reuses your framework's existing prop system instead of inventing new CSS class names to maintain. - Styles stay colocated. Editing
Button.jsxshows you the exact classes in use, right next to the markup they apply to — nothing to jump to. - Variants are just props. Adding a
variant="danger"prop that swaps inbg-red-600 hover:bg-red-700is a normal JavaScript conditional, not a second CSS class to define and remember.
When @apply still earns its keep
@apply remains genuinely useful in a few narrower cases: styling raw HTML you don't control the classes on (markdown-rendered content, a <style> block scoped to third-party widget markup), or a handful of truly global, low-level base styles. It's a poor substitute for component extraction in an actual application, but a fine tool at the edges where componentization isn't available.
/* Reasonable use: styling markdown-rendered content you don't add classes to directly */
.prose h2 {
@apply mt-8 mb-4 text-2xl font-bold text-slate-900;
}The general guidance from the Tailwind team itself is to reach for @apply sparingly — if you're using it to recreate a general-purpose class system across your whole app, you've mostly rebuilt the "named CSS classes" model Tailwind exists to move away from.