The @apply directive in Tailwind CSS lets you group utility classes into reusable custom classes in your CSS. That keeps repeated patterns in one place, shortens the class lists in your HTML, and still works with Tailwind’s responsive prefixes and state variants. This post covers when extracting custom classes is worth it and how to do it without losing the benefits of utility-first styling.
Best Times to Use Custom Classes
Using custom classes can make your code cleaner and more efficient. They’re particularly helpful when specific patterns or styles are reused across your project. The key is knowing when to extract utility classes into custom ones.
Spotting Repeated Utility Patterns
Take a close look at your codebase for utility combinations that pop up often. Tools like Tailwind CSS IntelliSense can make this process easier. For instance, if you find a repeated pattern like this:
<div class="bg-white shadow-lg rounded-lg p-6 m-4 hover:shadow-xl">
It’s a good idea to create a custom class for it:
.card {
@apply bg-white shadow-lg rounded-lg p-6 m-4 hover:shadow-xl;
}
By consolidating repeated styles into a single class, you simplify your code and reduce duplication.
Custom Classes vs. Utility Classes
Choosing between custom and utility classes depends on what you’re working on. Here’s a quick comparison:
| Scenario | Best Choice | Why? |
|---|---|---|
| Unique styles | Utility classes | Ideal for one-off designs |
| Complex components | Custom classes | Makes code easier to read and manage |
| Shared design patterns | Custom classes | Keeps styles consistent |
| Quick prototyping | Utility classes | Speeds up experimentation |
Think about whether your project needs highly reusable design tokens or more specific component classes.
Component Classes vs. Design Token Classes
Decide whether to use component-specific classes or design token classes based on your project’s structure. Here’s how they differ:
- Component-specific classes: Great for unique elements in your UI.
- Design token classes: Act as reusable building blocks for your design system.
Example of a component-specific class:
.product-card {
@apply bg-white p-4 shadow-md rounded-lg;
}
Example of a design token class:
.primary-button {
@apply bg-blue-500 text-white font-bold py-2 px-4 rounded hover:bg-blue-700;
}
Step-by-Step Guide to Using @apply
Follow these steps to use @apply effectively and keep your styles organized and easy to maintain.
Setting Up Custom Layers
Define custom style layers in your tailwind.config.js file to organize your styles:
module.exports = {
// ...other config
layers: {
components: true,
utilities: true,
},
}
These layers help prioritize styles:
| Layer | Purpose | Examples |
|---|---|---|
| Base | Foundational styles | Typography, resets |
| Components | Reusable design blocks | Buttons, cards, forms |
| Utilities | Single-use classes | Custom utility classes |
Creating and Using Custom Classes
Bundle common utility classes into reusable custom classes:
@layer components {
.btn-primary {
@apply py-2 px-4 bg-blue-500 text-white font-semibold rounded-lg;
@apply hover:bg-blue-700 focus:ring-2 focus:ring-blue-400;
}
}
Store these styles in separate files for better organization. This approach ensures modularity and makes your code easier to manage.
Adding Variants and Breakpoints
Use responsive prefixes and group related styles to handle different screen sizes and states:
.responsive-card {
@apply p-4 bg-white shadow-md;
@apply sm:p-6 sm:rounded-lg;
@apply md:p-8 md:shadow-lg;
@apply lg:max-w-3xl lg:mx-auto;
}
For components with multiple states, structure your styles logically:
.interactive-element {
@apply bg-gray-100 text-gray-800;
@apply hover:bg-gray-200 hover:text-gray-900;
@apply focus:outline-none focus:ring-2;
@apply dark:bg-gray-800 dark:text-gray-200;
}
Advanced @apply Methods
Taking custom classes a step further, these advanced techniques can make your CSS even more efficient and easier to manage.
State-Based Styling
You can use @apply to define styles for different states:
/* Button with interactive states */
.interactive-button {
@apply bg-blue-500 text-white px-4 py-2 rounded transition-colors;
@apply hover:bg-blue-600 active:bg-blue-700;
@apply focus:outline-none focus:ring-2 focus:ring-blue-300;
@apply disabled:bg-gray-400 disabled:cursor-not-allowed;
}
/* Basic form input styling */
.form-input {
@apply border-2 rounded px-3 py-2 w-full;
@apply focus:border-blue-500 focus:ring-1 focus:ring-blue-200;
}
For form validation states, you can create specific styles like this:
.input-field {
@apply border-2 rounded-md p-2;
}
.input-success {
@apply border-green-500 bg-green-50 text-green-700;
@apply focus:border-green-600 focus:ring-green-200;
}
.input-error {
@apply border-red-500 bg-red-50 text-red-700;
@apply focus:border-red-600 focus:ring-red-200;
}
Once you’ve mastered state-based styling, you can extend these ideas to themes and dark mode.
Theme and Dark Mode Setup
Switching themes becomes simple with CSS custom properties and @apply:
:root {
--bg-primary: theme('colors.white');
--text-primary: theme('colors.gray.900');
}
.dark {
--bg-primary: theme('colors.gray.900');
--text-primary: theme('colors.gray.100');
}
.themed-container {
@apply bg-[var(--bg-primary)] text-[var(--text-primary)];
@apply transition-colors duration-200;
}
For example, Vercel’s documentation team reported a 32% reduction in CSS bundle size and a 250ms improvement in First Contentful Paint on their documentation pages after implementing this method in September 2023 [1].
To ensure smooth performance, it’s crucial to optimize your @apply usage.
Optimizing @apply Performance
Here are some strategies to maintain performance while using @apply:
| Strategy | How to Implement | Benefits |
|---|---|---|
| Layer Organization | Use @layer for custom styles | Simplifies specificity management |
| Code Splitting | Separate theme-specific styles | Shrinks the initial bundle size |
| JIT Mode | Enable Tailwind’s JIT compiler | Generates styles on demand |
You can also group related styles into component classes for better performance:
@layer components {
.optimized-card {
@apply bg-white rounded-lg shadow-md p-4;
@apply dark:bg-gray-800 dark:shadow-none;
@apply hover:shadow-lg transition-shadow;
}
}
Lastly, enabling Tailwind’s JIT mode ensures that only the styles you need are generated, keeping your CSS lightweight and fast.
Making the Most of @apply
The @apply directive in Tailwind CSS is a handy feature for simplifying and organizing your web development workflow. It allows you to streamline repetitive utility classes into custom CSS, making your code easier to manage while keeping the benefits of utility-first design.
- Group repeated utility classes into custom classes to reduce clutter.
- Use
@applyfor dynamic components that require state-based styling. - Combine responsive utilities with custom classes to ensure consistency across different screen sizes.
For more complex components, you can pair @apply with state management. Here’s an example:
.interactive-component {
@apply bg-white rounded-lg shadow-md transition-all;
@apply hover:shadow-lg focus:ring-2 focus:ring-blue-500;
}