Episode 11 of 19

@apply Directive

Learn the Tailwind CSS @apply directive to extract repetitive utility patterns into reusable CSS classes. Understand when to use @apply, when to avoid it, and the @layer directive for organizing styles.

The Problem @apply Solves

As your project grows, you might notice the same combination of utility classes repeated across many elements. For example, every button might use bg-indigo-600 text-white font-semibold py-2 px-4 rounded-lg hover:bg-indigo-700. Copying this everywhere is error-prone.

The @apply directive lets you extract these utility patterns into a custom CSS class while keeping all the benefits of the Tailwind utility system.

Using @apply

In your CSS file (usually src/input.css), you can create custom classes that compose Tailwind utilities:

@tailwind base;
@tailwind components;
@tailwind utilities;

.btn {
  @apply font-semibold py-2 px-4 rounded-lg transition-colors duration-200;
}

.btn-primary {
  @apply bg-indigo-600 text-white hover:bg-indigo-700;
}

.btn-secondary {
  @apply bg-gray-200 text-gray-800 hover:bg-gray-300;
}

.btn-danger {
  @apply bg-red-600 text-white hover:bg-red-700;
}

.btn-outline {
  @apply border-2 border-indigo-600 text-indigo-600 hover:bg-indigo-600 hover:text-white;
}

Now your HTML becomes much cleaner:

<!-- Before @apply -->
<button class="bg-indigo-600 text-white font-semibold py-2 px-4 rounded-lg hover:bg-indigo-700">
  Save
</button>

<!-- After @apply -->
<button class="btn btn-primary">Save</button>
<button class="btn btn-secondary">Cancel</button>
<button class="btn btn-danger">Delete</button>
<button class="btn btn-outline">Learn More</button>

The @layer Directive

Tailwind processes styles in three layers in order: base, components, and utilities. Using the @layer directive tells Tailwind exactly where to place your custom styles, ensuring proper specificity.

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  /* Reset / global styles */
  h1 {
    @apply text-3xl font-bold;
  }
  h2 {
    @apply text-2xl font-semibold;
  }
  a {
    @apply text-indigo-600 hover:text-indigo-800;
  }
}

@layer components {
  /* Reusable component classes */
  .card {
    @apply bg-white rounded-xl shadow-md p-6;
  }
  .badge {
    @apply inline-flex items-center text-xs font-semibold px-3 py-1 rounded-full;
  }
  .badge-success {
    @apply bg-green-100 text-green-800;
  }
  .badge-error {
    @apply bg-red-100 text-red-800;
  }
}

@layer utilities {
  /* Custom one-off utilities */
  .text-shadow {
    text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
  }
  .text-shadow-lg {
    text-shadow: 4px 4px 8px rgba(0, 0, 0, 0.2);
  }
}

Why @layer Matters

  • @layer base — Styles that apply to raw HTML elements (like h1, a, body). These have the lowest specificity.
  • @layer components — Reusable component classes like .card, .btn. These can be overridden by utilities.
  • @layer utilities — Custom one-off utilities. These have the highest specificity and will always win.

Putting .btn in @layer components means you can still override it with utilities:

<!-- The px-8 utility overrides the px-4 from .btn -->
<button class="btn btn-primary px-8">Extra Wide Button</button>

Real-World Example — Form Styles

@layer components {
  .form-input {
    @apply w-full px-4 py-2 border border-gray-300 rounded-lg 
           text-gray-900 placeholder-gray-400
           focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent
           transition-shadow duration-200;
  }

  .form-label {
    @apply block text-sm font-medium text-gray-700 mb-1;
  }

  .form-group {
    @apply mb-5;
  }

  .form-error {
    @apply text-sm text-red-600 mt-1;
  }
}
<form class="max-w-md mx-auto">
  <div class="form-group">
    <label class="form-label">Email</label>
    <input type="email" class="form-input" placeholder="you@example.com">
  </div>
  <div class="form-group">
    <label class="form-label">Password</label>
    <input type="password" class="form-input" placeholder="••••••••">
    <p class="form-error">Password must be at least 8 characters.</p>
  </div>
  <button type="submit" class="btn btn-primary w-full">Sign In</button>
</form>

When to Use @apply

Good use cases:

  • Buttons, form inputs, and badges that appear dozens of times.
  • When using a templating system that does not support component abstraction.
  • Base styles for headings and links.

When to avoid @apply:

  • If you are using a component framework (React, Vue, Svelte) — extract a component instead. The component encapsulates both HTML and styles.
  • For one-off styles that appear only once or twice.
  • Do not overuse it — you lose the colocation advantage of utility-first CSS.

Components vs @apply

In a React or Vue project, it is generally better to create a component:

// React component approach (preferred)
function Button({ variant = 'primary', children, ...props }) {
  const styles = {
    primary: 'bg-indigo-600 text-white hover:bg-indigo-700',
    secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
    danger: 'bg-red-600 text-white hover:bg-red-700',
  };

  return (
    <button 
      className={`font-semibold py-2 px-4 rounded-lg ${styles[variant]}`}
      {...props}
    >
      {children}
    </button>
  );
}

This approach keeps your styles in JavaScript, making them easier to maintain alongside your component logic.

Recap

  • @apply extracts repetitive utility patterns into CSS classes.
  • @layer organizes custom styles into base, components, and utilities.
  • Use @layer components for reusable classes so utilities can still override them.
  • In component frameworks, prefer creating components over @apply.
  • Use @apply for elements that repeat heavily across templates.

Next, we will learn about CSS Grid utilities in Tailwind.

Web DevelopmentTailwind CSSCSSComponents@apply