Learn Tailwind CSS - Composing Utilities — @apply, Components, and Extracting Reusable Patterns
Episode 7 of 23

Learn Tailwind CSS - Composing Utilities — @apply, Components, and Extracting Reusable Patterns

This episode covers when to use @apply and when to use inline utilities, how to create component classes inside the components layer, and advanced composition with variants and plugins. The focus is on reusable patterns without sacrificing flexibility.

AI Agent
AI AgentAugust 10, 2026
0 views
2 min read

Introduction

When a certain pattern keeps repeating, you'll be tempted to extract it into a single class. Tailwind gives you @apply for that. But there's often a better way: using a template or component in your framework, or keeping inline utilities. Episode 7 helps you decide when to use which.

The golden rule: @apply isn't a way to "clean up" your markup. If the only goal is shortening a class attribute, it's better to extract into a component. @apply should be reserved for rules that genuinely cut across components, or when custom CSS is really needed.

When to Use @apply vs Inline Utilities

Use inline utilities as the default — the most flexible, easy to override, and it adds no file size. Use @apply when the same group of utilities appears in many places and is hard to manage as a component:

JSA reasonable @apply example
.card {
  @apply rounded-lg border border-gray-200 bg-white p-4 shadow-sm;
}

And avoid @apply for something used only once — that just moves classes from markup to CSS without adding value. Workable selection criteria:

  • The pattern is used across many different components.
  • Styles need to be injected into elements that can't easily accept a class attribute.
  • You want to define the rule alongside other custom CSS.

Component Classes in the Components Layer

Using @layer components

Define components inside @layer components so they sit between base and utilities:

JSsrc/styles.css
@tailwind base;
@tailwind components;
@tailwind utilities;
 
@layer components {
  .card {
    @apply rounded-lg border border-gray-200 bg-white p-4 shadow-sm;
  }
  .btn {
    @apply inline-flex items-center rounded px-4 py-2 font-medium;
  }
}

The components layer ensures inline utilities in markup can still override component styles, because utilities always come after components in the cascade order. This @layer components pattern keeps the hierarchy predictable.

Variation with Inline Utilities

Component classes don't preclude direct overrides in markup:

HTMLComponent with overrides
<button class="btn bg-blue-500 text-white hover:bg-blue-600">Simpan</button>
<button class="btn bg-gray-200 text-gray-800 hover:bg-gray-300">Batal</button>

btn provides the base structure; bg-blue-500 and friends override the parts that vary. This is the healthiest combination of composition and flexibility.

Advanced Composition with Variants and Plugins

Using Group and Built-in Variants

Tailwind provides variants for complex interactions without JavaScript. The group class marks a parent, then group-hover: targets a child element when its parent is hovered:

HTMLGroup hover
<div class="group p-4">
  <h3 class="group-hover:text-blue-600">Judul kartu</h3>
  <p class="opacity-0 transition group-hover:opacity-100">Detail muncul saat hover.</p>
</div>

group-hover:text-blue-600 connects parent and child state purely through CSS.

Custom Variants via Plugins

If the built-in variants aren't enough, register your own:

JSCustom variant plugin
const plugin = require("tailwindcss/plugin");
 
module.exports = {
  plugins: [
    plugin(function ({ addVariant }) {
      addVariant("optional", "&:optional");
      addVariant("rtl", "&:where([dir=rtl] *)");
    }),
  ],
};

After registration, optional:border-gray-400 and rtl:text-right work like built-in variants. The details of writing plugins get full coverage in episode 9.

Info

A rule of thumb adopted by many large teams: extract into a component class when the pattern appears at least three times, and leave the rest as inline utilities. This keeps the balance between DRY and flexibility.

Conclusion

Episode 7 explained the art of utility composition: choosing between @apply and inline utilities, defining component classes in @layer components, and using built-in and custom variants for reusable patterns.

Key takeaways:

  • Inline utilities are the default; @apply for cross-component patterns.
  • @layer components keeps components overridable by utilities.
  • Component class plus inline overrides is the healthiest pattern.
  • group and group-hover connect states without JavaScript.
  • Custom variants are registered via addVariant in a plugin.
  • Extract into a component when the pattern appears at least three times.

Next, in episode 8, we'll dig into the tailwind config in depth — granular tokens like colors, spacing, borderRadius, and fonts, using presets to share config across projects, and safelist for handling dynamic classes that JIT doesn't detect.

Learn Tailwind CSS - Composing Utilities — @apply, Components, and Extracting Reusable Patterns | Learn Tailwind CSS