| |

Tailwind CSS 43 ๐ŸŽจ Reusing Utility Sets with @apply and Custom Utility Definitions

Utility-first CSS has a well-known friction point. When the same set of utilities appears on every instance of a component โ€” a button, a card, a badge โ€” the markup is repeated, and a change to the pattern requires editing every occurrence. The @apply directive is Tailwind’s answer. It takes a list of utility classes and inlines their declarations into a CSS rule. The rule is a class of its own, and the HTML uses the class instead of the utility list. The styles are the same; the source is shorter, and the pattern is defined once.

The @apply directive has a contentious history. For years, the Tailwind documentation warned against using it, on the grounds that extracting component classes re-creates the abstraction that utility-first CSS was designed to avoid. The concern was real: a project that wraps every utility group in a class loses the consistency and composability of the utility system, and the CSS grows into a parallel design system that duplicates Tailwind’s. The v4 documentation softened the guidance. It now describes @apply as “useful” for small, highly reusable patterns, while continuing to recommend utility composition in the markup for most cases. The tool is not banned; it is a targeted escape hatch for the small number of cases where the repetition is genuinely a problem.

The mechanics changed in v4. In v3, a custom class was written inside @layer components or @layer utilities, and @apply worked because Tailwind intercepted the layer. In v4, @layer is the native CSS cascade layer, and it does not register classes with Tailwind. The @utility directive is the replacement. A class defined with @utility is registered with Tailwind, supports variants like hover: and md:, is purged when unused, and can be used with @apply inside other rules. A class defined in @layer components is plain CSS: it does not support variants, it is not purged, and @apply inside it fails with “Cannot apply unknown utility class.”

This chapter covers three areas. First, why @apply and custom utilities exist โ€” the repetition problem and the tension between utility composition and component abstraction. Second, how @apply works in v4 โ€” the syntax, the restriction to registered utilities, the use of @reference in Vue and Svelte scoped styles, and the @utility directive for reusable custom classes. Third, the patterns for reuse โ€” when to use @apply, when to define a custom utility, when to keep the utilities in the markup, and how the three approaches differ in variants, purge, and maintainability. The chapter ends with a complete example session, a quick reference, best practices, common pitfalls, real-world examples, and diagrams showing the @apply compilation and the reuse decision.

Key point: @apply inlines utility declarations into a CSS rule. In v4, the utilities must be registered with Tailwind, and custom classes must be defined with @utility, not @layer components. Use @apply for small, genuinely repeated patterns, not as a default. Prefer utility composition in the markup for most cases, and use @utility when a custom class needs variant support, purge tracking, or @apply compatibility.


Why @apply and custom utilities exist

The repetition problem. A button uses px-4 py-2 rounded font-medium bg-blue-500 text-white hover:bg-blue-600. A card uses bg-white rounded-lg shadow-md p-6. A badge uses inline-flex items-center rounded-full px-2 py-1 text-xs font-medium. The lists are long, and they appear on every instance. The markup is noisy, and a change to the pattern โ€” a different border radius, a different hover color โ€” requires finding every occurrence and editing each one. The repetition is the problem that component classes were designed to solve, and @apply brings that solution to Tailwind.

The utility-composition argument. The counter-argument is that repetition in the markup is not always a problem. Two buttons that look the same today may need to diverge tomorrow. A class that wraps the utilities locks them into a single unit, and the divergence requires either a new class or an override. Utility composition keeps the styles at the point of use, so each instance can vary independently. The Tailwind documentation’s recommendation is to keep the utilities in the markup until the repetition is genuinely painful, and to use @apply only for the small number of patterns that are truly stable and truly repeated.

The @utility directive problem. In v3, a custom component class was written in @layer components, and @apply worked inside it. In v4, @layer components is a native CSS layer, and Tailwind does not intercept it. A class defined in that layer is not registered with Tailwind: it does not support variants, it is not purged, and @apply inside it fails. The @utility directive is the replacement. A class defined with @utility is registered, supports variants, is purged, and works with @apply. The directive is the v4 answer to the question that @layer components answered in v3.

The scoped-style problem. A Vue or Svelte component uses <style scoped> blocks. The scoped styles are compiled into a separate stylesheet that is processed independently of the main Tailwind import. @apply inside the scoped block fails with “Cannot apply unknown utility class,” because the compiler does not know about Tailwind’s utilities. The @reference directive is the fix: @reference "../app.css"; at the top of the scoped block tells Tailwind to resolve the utilities from the referenced stylesheet. The directive is a v4 addition, and it is required for @apply inside scoped styles in component-based frameworks.

The maintainability problem. A custom class is a name for a set of styles. The name is an abstraction, and an abstraction has a cost. The reader must know what the name means, and the name must be updated when the styles change. A utility list is explicit: the reader sees the styles directly. The trade-off is between the brevity of a name and the explicitness of a utility list. The name is better when the pattern is stable and repeated; the utility list is better when the pattern is variable or used once.

The trade-off. @apply and custom utilities are a tool, not a doctrine. The Tailwind team’s position is that utility composition in the markup is the default, and @apply is for the cases where the repetition is genuinely a problem. The criteria are: the pattern is used in many places, the pattern is stable, and the pattern is small enough to be a single class. A button with a fixed style is a candidate. A layout container with a variable number of children is not. The trade-off is between the abstraction of a class and the explicitness of a utility list.


a. The @apply directive

@apply takes a list of utility classes and inlines their declarations into the containing CSS rule. The rule becomes a class of its own, and the HTML uses the class.

@import "tailwindcss";

.btn {
  @apply px-4 py-2 rounded font-medium bg-blue-500 text-white;
  @apply hover:bg-blue-600 focus:ring-2 focus:ring-blue-300;
}

The .btn class contains the declarations of all the listed utilities. The hover: and focus: variants are supported: @apply hover:bg-blue-600 inlines the hover state as a nested rule. The class is a plain CSS class โ€” it is not registered with Tailwind, it does not support md:btn or dark:btn, and it is not purged when unused. It is the v3 @layer components pattern without the layer.

The utilities in the @apply list must be registered with Tailwind. A utility generated by Tailwind โ€” px-4, rounded, bg-blue-500 โ€” is registered. A custom class defined in @layer components is not, and @apply on it fails. A custom class defined with @utility is registered, and @apply on it works.

The @apply directive can be used inside any CSS rule, including inside a @layer block:

@layer components {
  .card {
    @apply bg-white rounded-lg shadow-md p-6;
  }
}

The .card class is in the components layer, so it is weaker than Tailwind’s utilities. A p-8 utility on the same element overrides the p-6 from the card. This is the intended behavior: the component provides defaults, and the utilities override them.

The !important modifier can be appended to a utility in the @apply list:

.force-red {
  @apply text-red-500!;
}

The ! suffix makes the declaration important. This is useful for overriding third-party styles that are themselves important, but it should be used sparingly.


b. The @utility directive for reusable custom classes

The @utility directive creates a custom utility that is registered with Tailwind. The class supports variants, is purged when unused, and works with @apply inside other rules.

@utility btn {
  @apply px-4 py-2 rounded font-medium;
  background-color: var(--color-primary-600);
  color: var(--color-white);
}

@utility btn-secondary {
  @apply px-4 py-2 rounded font-medium;
  background-color: var(--color-gray-100);
  color: var(--color-gray-900);
}

The btn class is a registered utility. It can be used in the markup as class="btn", with variants as class="hover:btn" or class="md:btn", and with @apply inside another rule. It is purged when no markup uses it.

The @utility directive can use @variant for state variants inside the definition:

@utility btn {
  padding: calc(var(--spacing) * 2) calc(var(--spacing) * 4);
  border-radius: var(--radius-md);
  background-color: var(--color-primary-600);

  @variant hover {
    background-color: var(--color-primary-700);
  }

  @variant focus-visible {
    outline: 2px solid var(--color-primary-400);
    outline-offset: 2px;
  }
}

The @variant directive applies a state variant inside the utility. The hover and focus-visible states are compiled into the utility’s CSS. The utility is a single class, and the variants are part of its definition.

A custom utility can be composed with other utilities in the markup. The utility provides the base styles, and the markup adds overrides:

<button class="btn bg-red-500">Delete</button>

The btn class provides the padding, radius, and typography. The bg-red-500 utility overrides the background color. The composition works because the utility is weaker than the utilities layer โ€” the btn class is in the utilities layer, and bg-red-500 is also in the utilities layer, but the source order determines the winner. Tailwind sorts the utilities within the layer, and bg-red-500 comes after btn in the generated CSS, so it wins.


c. The @reference directive for scoped styles

A Vue, Svelte, or Astro component with a <style scoped> block compiles its styles into a separate stylesheet. The stylesheet does not import Tailwind, so @apply fails with “Cannot apply unknown utility class.” The @reference directive fixes this by telling Tailwind to resolve the utilities from a referenced stylesheet.

<template>
  <button class="btn">Click</button>
</template>

<style scoped>
@reference "../app.css";

.btn {
  @apply px-4 py-2 rounded font-medium bg-blue-500 text-white;
}
</style>

The @reference "../app.css"; line imports the main Tailwind stylesheet into the scoped block’s compilation context. The @apply directive resolves the utilities from that context. The referenced stylesheet is not duplicated in the output; it is only used for resolution. This is the v4 pattern for @apply in scoped styles, and it is required for component frameworks that isolate their styles.

The @reference directive is also useful for @apply in a CSS module or a component-level stylesheet that does not import Tailwind directly. The directive makes the utilities available without duplicating the styles.


Complete Example Session

/* ============================================
   PART 1: A BASIC @apply
   ============================================ */

@import "tailwindcss";

.btn {
  @apply px-4 py-2 rounded font-medium bg-blue-500 text-white;
}

/* The .btn class contains the declarations of all
   the listed utilities. The HTML uses class="btn". */


/* ============================================
   PART 2: @apply WITH VARIANTS
   ============================================ */

.btn {
  @apply px-4 py-2 rounded font-medium bg-blue-500 text-white;
  @apply hover:bg-blue-600 focus:ring-2 focus:ring-blue-300;
}

/* The hover: and focus: utilities are supported.
   They compile into nested rules inside .btn. */


/* ============================================
   PART 3: A CUSTOM UTILITY
   ============================================ */

@utility card {
  background-color: var(--color-white);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  padding: calc(var(--spacing) * 6);
}

/* The card class is registered with Tailwind.
   It supports variants: hover:card, md:card.
   It is purged when unused. It works with @apply. */


/* ============================================
   PART 4: @apply INSIDE @utility
   ============================================ */

@utility btn {
  @apply px-4 py-2 rounded font-medium;
  background-color: var(--color-primary-600);
  color: var(--color-white);
}

/* @apply works inside @utility because the class
   is registered with Tailwind. */


/* ============================================
   PART 5: @variant INSIDE @utility
   ============================================ */

@utility btn {
  padding: calc(var(--spacing) * 2) calc(var(--spacing) * 4);
  border-radius: var(--radius-md);
  background-color: var(--color-primary-600);

  @variant hover {
    background-color: var(--color-primary-700);
  }

  @variant focus-visible {
    outline: 2px solid var(--color-primary-400);
    outline-offset: 2px;
  }
}


/* ============================================
   PART 6: THE FAILURE CASE โ€” @layer components
   ============================================ */

@layer components {
  .btn {
    @apply px-4 py-2 rounded; /* FAILS in v4 */
  }
}

/* Error: Cannot apply unknown utility class: px-4
   The .btn class is not registered with Tailwind.
   @apply cannot resolve the utilities.
   Fix: use @utility instead of @layer components. */


/* ============================================
   PART 7: THE @reference DIRECTIVE
   ============================================ */

/* In a Vue or Svelte component with <style scoped>: */

/*
<style scoped>
@reference "../app.css";

.btn {
  @apply px-4 py-2 rounded font-medium bg-blue-500 text-white;
}
</style>
*/

/* The @reference directive resolves the utilities
   from the main Tailwind stylesheet.
   Without it, @apply fails in the scoped block. */


/* ============================================
   PART 8: COMPOSING A CUSTOM UTILITY IN THE MARKUP
   ============================================ */

/* <button class="btn bg-red-500">Delete</button>
   The btn class provides padding, radius, typography.
   The bg-red-500 utility overrides the background.
   The composition works because both are in the
   utilities layer, and bg-red-500 comes later. */


/* ============================================
   PART 9: THE THREE APPROACHES
   ============================================ */

/* APPROACH 1: Utility composition in the markup
   <button class="px-4 py-2 rounded font-medium bg-blue-500 text-white">
   Explicit. Each instance can vary. No abstraction. */

/* APPROACH 2: @apply for a plain CSS class
   .btn { @apply px-4 py-2 rounded; }
   <button class="btn">
   Shorter markup. Plain CSS class. No variants. */

/* APPROACH 3: @utility for a registered custom utility
   @utility btn { @apply px-4 py-2 rounded; }
   <button class="btn md:btn hover:btn">
   Shorter markup. Registered utility. Variants work. Purged. */


/* ============================================
   PART 10: THE COMPLETE STYLESHEET
   ============================================ */

@import "tailwindcss";

@layer base {
  :root {
    --color-primary-600: oklch(0.55 0.2 250);
    --color-primary-700: oklch(0.48 0.2 250);
  }
}

@utility card {
  background-color: var(--color-white);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  padding: calc(var(--spacing) * 6);

  @variant hover {
    box-shadow: var(--shadow-lg);
  }
}

@utility btn {
  @apply px-4 py-2 rounded font-medium;
  background-color: var(--color-primary-600);
  color: var(--color-white);

  @variant hover {
    background-color: var(--color-primary-700);
  }
}

@utility badge {
  @apply inline-flex items-center rounded-full px-2 py-1 text-xs font-medium;
  background-color: var(--color-gray-100);
  color: var(--color-gray-800);
}

The ten parts show a basic @apply, @apply with variants, a custom utility, @apply inside @utility, @variant inside @utility, the failure case with @layer components, the @reference directive, composing a custom utility in the markup, the three approaches, and the complete stylesheet.


Quick Reference

@apply Syntax

FormEffect
@apply px-4 py-2Inlines the utility declarations
@apply hover:bg-blue-600Inlines the hover state
@apply text-red-500!Inlines with !important
@apply inside @utilityWorks (class is registered)
@apply inside @layer componentsFails in v4

@utility Syntax

FormEffect
@utility name { ... }Creates a registered utility
@variant hover { ... }State variant inside the utility
@apply ... inside @utilityWorks
md:name, hover:nameVariants work
PurgeUnused utilities are removed

The Three Approaches

ApproachMarkupVariantsPurgedExplicit
Utility compositionLongYesYesYes
@apply plain classShortNoNoNo
@utilityShortYesYesNo

@reference

ContextDirective
Vue <style scoped>@reference "../app.css";
Svelte <style>@reference "../app.css";
CSS module@reference "../app.css";
Plain CSSNot needed

Best Practices

โœ… Do This:

/* Use @utility for reusable custom classes */
@utility btn { @apply px-4 py-2 rounded; }                                // โœ…
/* Use @apply for small, stable patterns */
.btn { @apply px-4 py-2 rounded font-medium; }                            // โœ…
/* Use @variant inside @utility for state variants */
@utility btn { @variant hover { ... } }                                   // โœ…
/* Use @reference in scoped styles */
@reference "../app.css";                                                  // โœ…
/* Keep the utilities in the markup for one-off styles */
<button class="px-4 py-2 rounded">                                        // โœ…

โŒ Don’t Do This:

/* Don't use @layer components for custom utilities in v4 */
@layer components { .btn { @apply ...; } } // fails                        // โŒ
/* Don't wrap every utility group in a class */
.btn { @apply ...; } .card { @apply ...; } .badge { @apply ...; }        // โŒ
/* Don't use @apply for a pattern used once */
.one-off { @apply mt-4 mb-2; } // just use the utilities in the markup     // โŒ
/* Don't forget @reference in scoped styles */
<style scoped>.btn { @apply px-4; }</style> // fails                       // โŒ
/* Don't use @apply for dynamic or variable patterns */
/* Use utility composition instead */                                     // โŒ

Common Pitfalls

PitfallWhy It HappensFix
@apply fails in @layer componentsClass not registeredUse @utility
@apply fails in scoped stylesNo reference to TailwindAdd @reference
Custom class has no variantsDefined in @layer, not @utilityUse @utility
Class not purgedDefined in @layerUse @utility
Over-abstractionWrapping every utility groupKeep utilities in the markup
!important overuseFighting specificityFix the layer order
Utility not foundTypo or unregisteredCheck the utility name

Real-World Examples

1. Basic @apply

.btn { @apply px-4 py-2 rounded font-medium; }

2. @apply with Hover

.btn { @apply hover:bg-blue-600; }

3. Custom Utility

@utility card { padding: calc(var(--spacing) * 6); }

4. @apply inside @utility

@utility btn { @apply px-4 py-2 rounded; }

5. @variant inside @utility

@utility btn { @variant hover { background-color: var(--color-primary-700); } }

6. @reference in Vue

@reference "../app.css";

7. Composing in the Markup

<button class="btn bg-red-500">Delete</button>

8. The Failure Case

@layer components { .btn { @apply px-4; } } /* fails */

9. Utility Composition

<button class="px-4 py-2 rounded font-medium bg-blue-500 text-white">

10. Complete Utility

@utility badge {
  @apply inline-flex items-center rounded-full px-2 py-1 text-xs font-medium;
}

Visual

The @apply Compilation

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE @apply COMPILATION                                      โ”‚
โ”‚                                                              โ”‚
โ”‚  Source:                                                     โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  .btn {                                              โ”‚    โ”‚
โ”‚  โ”‚    @apply px-4 py-2 rounded font-medium;             โ”‚    โ”‚
โ”‚  โ”‚  }                                                   โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚    โ”‚                                                         โ”‚
โ”‚    โ–ผ                                                         โ”‚
โ”‚  Compiled:                                                   โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  .btn {                                              โ”‚    โ”‚
โ”‚  โ”‚    padding-left: 1rem;                               โ”‚    โ”‚
โ”‚  โ”‚    padding-right: 1rem;                              โ”‚    โ”‚
โ”‚  โ”‚    padding-top: 0.5rem;                              โ”‚    โ”‚
โ”‚  โ”‚    padding-bottom: 0.5rem;                           โ”‚    โ”‚
โ”‚  โ”‚    border-radius: 0.25rem;                           โ”‚    โ”‚
โ”‚  โ”‚    font-weight: 500;                                 โ”‚    โ”‚
โ”‚  โ”‚  }                                                   โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                              โ”‚
โ”‚  The utility declarations are inlined.
โ”‚  The class is a plain CSS class.                             โ”‚
โ”‚  Variants in the @apply list compile to nested rules.        โ”‚
โ”‚                                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

@layer components vs @utility

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  @layer components vs @utility                               โ”‚
โ”‚                                                              โ”‚
โ”‚  @layer components:                                          โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  @layer components {                                 โ”‚    โ”‚
โ”‚  โ”‚    .btn { @apply px-4 py-2; }  โ† FAILS in v4         โ”‚    โ”‚
โ”‚  โ”‚  }                                                   โ”‚    โ”‚
โ”‚  โ”‚                                                       โ”‚    โ”‚
โ”‚  โ”‚  Plain CSS layer. Not registered with Tailwind.      โ”‚    โ”‚
โ”‚  โ”‚  No variants. No purge. @apply fails.                โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                              โ”‚
โ”‚  @utility:                                                   โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  @utility btn {                                      โ”‚    โ”‚
โ”‚  โ”‚    @apply px-4 py-2;  โ† WORKS                        โ”‚    โ”‚
โ”‚  โ”‚  }                                                   โ”‚    โ”‚
โ”‚  โ”‚                                                       โ”‚    โ”‚
โ”‚  โ”‚  Registered with Tailwind.                           โ”‚    โ”‚
โ”‚  โ”‚  Variants: hover:btn, md:btn.                        โ”‚    โ”‚
โ”‚  โ”‚  Purged when unused. @apply works.                   โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Three Approaches

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE THREE APPROACHES                                        โ”‚
โ”‚                                                              โ”‚
โ”‚  1. UTILITY COMPOSITION:                                     โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  <button class="px-4 py-2 rounded font-medium        โ”‚    โ”‚
โ”‚  โ”‚    bg-blue-500 text-white hover:bg-blue-600">        โ”‚    โ”‚
โ”‚  โ”‚                                                       โ”‚    โ”‚
โ”‚  โ”‚  Explicit. Each instance can vary.                   โ”‚    โ”‚
โ”‚  โ”‚  Variants work. No abstraction.                      โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                              โ”‚
โ”‚  2. @apply PLAIN CLASS:                                      โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  .btn { @apply px-4 py-2 rounded font-medium; }      โ”‚    โ”‚
โ”‚  โ”‚  <button class="btn">                                โ”‚    โ”‚
โ”‚  โ”‚                                                       โ”‚    โ”‚
โ”‚  โ”‚  Shorter markup. Plain CSS class.                    โ”‚    โ”‚
โ”‚  โ”‚  No variants on .btn. Not purged.                    โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                              โ”‚
โ”‚  3. @utility:                                                โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  @utility btn { @apply px-4 py-2 rounded font-medium; }โ”‚   โ”‚
โ”‚  โ”‚  <button class="btn md:btn hover:btn">               โ”‚    โ”‚
โ”‚  โ”‚                                                       โ”‚    โ”‚
โ”‚  โ”‚  Shorter markup. Registered utility.                 โ”‚    โ”‚
โ”‚  โ”‚  Variants work. Purged. @apply works.                โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                              โ”‚
โ”‚  Approach 1 is the default. Approach 3 is for repeated,      โ”‚
โ”‚  stable patterns. Approach 2 is for one-off abstractions.    โ”‚
โ”‚                                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The @reference Directive

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE @reference DIRECTIVE                                    โ”‚
โ”‚                                                              โ”‚
โ”‚  WITHOUT @reference:                                         โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  <style scoped>                                      โ”‚    โ”‚
โ”‚  โ”‚  .btn { @apply px-4 py-2; }  โ† FAILS                 โ”‚    โ”‚
โ”‚  โ”‚  </style>                                            โ”‚    โ”‚
โ”‚  โ”‚                                                       โ”‚    โ”‚
โ”‚  โ”‚  Error: Cannot apply unknown utility class: px-4     โ”‚    โ”‚
โ”‚  โ”‚  The scoped stylesheet does not import Tailwind.     โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                              โ”‚
โ”‚  WITH @reference:                                            โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  <style scoped>                                      โ”‚    โ”‚
โ”‚  โ”‚  @reference "../app.css";                            โ”‚    โ”‚
โ”‚  โ”‚  .btn { @apply px-4 py-2; }  โ† WORKS                 โ”‚    โ”‚
โ”‚  โ”‚  </style>                                            โ”‚    โ”‚
โ”‚  โ”‚                                                       โ”‚    โ”‚
โ”‚  โ”‚  The directive resolves utilities from app.css.      โ”‚    โ”‚
โ”‚  โ”‚  The styles are not duplicated.                      โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                              โ”‚
โ”‚  Required for Vue, Svelte, and Astro scoped styles in v4.    โ”‚
โ”‚                                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
@applyInlines utility declarations into a CSS rule
@utilityCreates a registered custom utility
@variantState variant inside @utility
@referenceResolves utilities in scoped styles
@layer componentsPlain CSS layer; @apply fails in v4
VariantsWork with @utility, not with plain @apply classes
PurgeRegistered utilities are purged; plain classes are not
Default approachUtility composition in the markup
@apply use caseSmall, stable, repeated patterns
@utility use caseReusable utilities with variant support

Key takeaways:

  • @apply inlines utility declarations into a CSS rule. The rule becomes a class, and the HTML uses the class instead of the utility list. The styles are the same; the source is shorter.
  • In v4, the utilities must be registered with Tailwind. A custom class defined with @utility is registered. A class defined in @layer components is not, and @apply inside it fails with “Cannot apply unknown utility class.”
  • @utility is the v4 replacement for @layer components and @layer utilities. A class defined with @utility supports variants, is purged when unused, and works with @apply. A class in @layer is plain CSS.
  • @reference is required for @apply in scoped styles. A Vue, Svelte, or Astro component with a <style scoped> block does not import Tailwind, so @apply fails. The @reference directive resolves the utilities from the main stylesheet without duplicating it.
  • Utility composition in the markup is the default. The Tailwind documentation recommends keeping the utilities at the point of use for most cases. @apply is for the small number of patterns that are genuinely repeated and stable.
  • A custom utility can be composed in the markup. class="btn bg-red-500" applies the btn styles and then overrides the background with bg-red-500. The composition works because both are in the utilities layer, and the source order determines the winner.
  • @variant inside @utility handles state. The hover and focus-visible states are compiled into the utility’s CSS, so the utility is a single class with its states built in.
  • The abstraction has a cost. A custom class is a name for a set of styles, and the reader must know what the name means. The name is better when the pattern is stable and repeated; the utility list is better when the pattern is variable or used once.

Remember: @apply and @utility are tools for reuse, not doctrines. @apply inlines utilities into a CSS rule, and @utility creates a registered custom utility that supports variants, is purged, and works with @apply. In v4, custom classes must be defined with @utility, not @layer components. Scoped styles require @reference. The default approach is utility composition in the markup; @apply and @utility are for the small number of patterns that are genuinely repeated and stable. The trade-off is between the brevity of a name and the explicitness of a utility list.



Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!