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.

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.
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:
.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:
Define components inside @layer components so they sit between base and utilities:
@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.
Component classes don't preclude direct overrides in markup:
<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.
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:
<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.
If the built-in variants aren't enough, register your own:
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.
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:
@apply for cross-component patterns.@layer components keeps components overridable by utilities.group and group-hover connect states without JavaScript.addVariant in a plugin.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.