The Power of @apply: When and How to Extract Custom Classes

Learn how to simplify your Tailwind CSS workflow with the @apply directive for cleaner code and reusable styles.

Mar 1, 2025
The Power of @apply: When and How to Extract Custom Classes

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:

ScenarioBest ChoiceWhy?
Unique stylesUtility classesIdeal for one-off designs
Complex componentsCustom classesMakes code easier to read and manage
Shared design patternsCustom classesKeeps styles consistent
Quick prototypingUtility classesSpeeds 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:

LayerPurposeExamples
BaseFoundational stylesTypography, resets
ComponentsReusable design blocksButtons, cards, forms
UtilitiesSingle-use classesCustom 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:

StrategyHow to ImplementBenefits
Layer OrganizationUse @layer for custom stylesSimplifies specificity management
Code SplittingSeparate theme-specific stylesShrinks the initial bundle size
JIT ModeEnable Tailwind’s JIT compilerGenerates 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 @apply for 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;
}
Share this post

Supercharge your web development workflow

Take your productivity to the next level, Today!

Written by
Himanshu Mishra
Himanshu Mishra

Indie Maker and Founder @ UnveelWorks & Hoverify