Hello! Welcome back.
In our last lesson, we mastered a powerful pattern for creating configurable components by applying styles conditionally with a cn utility function. This approach keeps all the styling logic right inside our React components, which is excellent for co-location.
Today, we're going to explore a different, complementary method for organizing styles. Instead of managing style variations within a component's JSX, we'll learn how to extract common combinations of utilities into custom, reusable CSS classes. This lesson will teach you how to organize reusable style patterns using Tailwind's @apply directive.
You'll learn what problem @apply solves, how to use it correctly in your CSS, and most importantly, the trade-offs involved and when you should (and should not) use it in a modern React application.
The Problem: Repetitive Utility Classes
As you build an application, you'll often find yourself repeating the same long list of utility classes for similar elements. For instance, every button in your app might share a dozen classes for padding, font-weight, transitions, and focus states.
This repetition can make your HTML feel cluttered and harder to maintain. If you decide to change the default button styling, you'd have to find and update every single instance. This is where extracting these patterns can be helpful.
The following video from the creators of Tailwind CSS introduces this exact problem and sets the stage for the @apply directive.
05: Composing Utilities with @apply – Tailwind CSS v2.0: From Zero to Production
Let's start by understanding the problem that @apply is designed to solve.
Watch the first 40 seconds of this video. The presenter highlights the issue of long, repetitive class lists on an example button.
The Solution: Extracting Classes with @apply
The @apply directive allows you to take a set of Tailwind utility classes and compose them into a new, custom CSS class. You do this in your main CSS file (which in a Vite project is typically src/index.css).
The syntax is straightforward:
.my-custom-class {
@apply utility-class-1 utility-class-2 hover:utility-class-3;
}
This tells Tailwind to take all the CSS rules from the listed utilities and bake them into the .my-custom-class selector.
Controlling the Cascade with @layer
A crucial detail is where you define your custom class. To ensure you can still override your new class with other utilities in your HTML (e.g., <button class="btn-primary mt-8">), you must place your custom definitions in the correct "layer".
Tailwind provides the @layer directive for this. By wrapping your custom classes in @layer components { ... }, you ensure they are injected before Tailwind's utilities layer, maintaining the expected CSS specificity.
This next video segment demonstrates how to use @apply and @layer together to create a reusable button class.
05: Composing Utilities with @apply – Tailwind CSS v2.0: From Zero to Production
Now, let's see how @apply works in practice and learn about the important @layer directive.
Continue watching the same video from 00:40 to 03:04. Pay close attention to how @apply is used and why the @layer components directive is necessary for maintaining override capability.
Applying the Pattern in a React Component
Let's see how this looks in a React project.
1. Define the class in your CSS:
In your src/index.css file:
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn-primary {
@apply py-2 px-4 bg-blue-600 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75 transition-colors;
}
}
2. Use the class in your JSX:
Now, you can simplify your React component significantly.
// Before
function MyComponent() {
return (
<button className="py-2 px-4 bg-blue-600 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75 transition-colors">
Click Me
</button>
);
}
// After
function MyComponent() {
return (
<button className="btn-primary">
Click Me
</button>
);
}
As you can see, the HTML is cleaner, and you now have a single source of truth for your primary button style in index.css. This process is well-described in the official Tailwind documentation.
The official documentation provides a concise guide to this pattern. It reinforces what you just saw in the video.
Read the section 'Extracting classes with @apply'. It provides a clear HTML and CSS example of creating a btn-primary class.
The Trade-Offs: Avoiding Premature Abstraction
At this point, you might be tempted to use @apply for everything. If you find yourself having this thought, stop. Using @apply excessively is one of the most common anti-patterns for Tailwind developers.
When you abstract utilities into a CSS class, you trade the explicitness of utility-first CSS for the indirection of traditional CSS. This can have several downsides:
- You lose context in your HTML. You now have to jump between your
.jsxand.cssfiles to understand what a.btn-primaryactually looks like. - Your CSS bundle size increases. Utilities are reused, but every
@applyrule generates a new block of CSS. - You start writing CSS again. This negates the main workflow benefit of Tailwind, which is staying in your markup and composing styles without having to invent class names.
The following resources provide an essential perspective on when and why you should be cautious with @apply.
05: Composing Utilities with @apply – Tailwind CSS v2.0: From Zero to Production
The same video from Tailwind Labs now discusses the potential downsides of overusing @apply. This is a critical part of the lesson.
Watch the rest of the video, from 03:04 to the end (10:00). Notice how abstracting styles for primary and secondary buttons leads to more complexity and duplication in the CSS file itself. The final recommendation is key.
The official documentation powerfully summarizes this sentiment.
To reinforce this point, please read the official guidance on this topic. This is arguably the most important section of this lesson.
Read the section titled 'Avoiding premature abstraction'. It clearly lists the reasons why you should not overuse @apply.
@apply vs. Component Variants: The React Way
So, when should you use @apply versus the component variant pattern we learned in the last lesson? Here's a practical guideline for working in React:
-
Use Component Variants with
cn(Previous Lesson) for most cases. This is the preferred pattern for building reusable components in React. It keeps styling logic co-located with the component's structure and behavior. It's perfect for components with multiple variants (primary,secondary), sizes (sm,lg), or states (disabled,active). -
Use
@applysparingly for small, truly global, single-element patterns. Good candidates are things that you might want to apply to a raw<a>or<button>tag without wrapping it in a React component. For example, you could create a.proseclass for styling markdown content or a simple.link-stylefor all hyperlinks.
In short, your Button.jsx component from the last lesson is a more scalable and maintainable pattern than creating .btn-primary, .btn-secondary, .btn-sm, etc., with @apply.
Conclusion
Today you've learned about a powerful, but sharp, tool in the Tailwind toolkit. The @apply directive is excellent for its specific purpose, but understanding its trade-offs is key to using Tailwind effectively.
Key Takeaways:
@applylets you compose multiple utility classes into a single, reusable CSS class.- Always use
@layer componentsto ensure your custom classes can be overridden by utilities. - Use
@applywith caution. It should be reserved for small, highly reusable, atomic patterns like a base button style. Overusing it undermines the benefits of Tailwind's utility-first workflow. - In a React application, building components with conditional classes (using a utility like
cn) is often a more scalable and maintainable approach for handling variants and states than creating many custom CSS classes with@apply.
Next Up:
In this module, we've focused entirely on making our styling logic reusable, both inside components and in our CSS. In the next module, we will shift our focus from reusable styles to reusable logic. You will learn how to identify repetitive stateful logic in your components and extract it into your very own custom hooks.