# @apply Is Not How You Share Styles

**NEVER** · **ID:** `layout-no-apply-abstraction` · **Category:** layout
**Source:** [Tailwind](https://tailwindcss.com/docs)
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/layout-no-apply-abstraction

> NEVER: Collapse repeated markup into a `.btn { @apply … }` class to make it look "cleaner" — it reintroduces exactly what utilities removed: you invent a name, you jump between files to read the button's appearance, and one edit silently repaints every button. It also collapses at the first variant (`.btn-primary`, `.btn-sm`, disabled), where you start hand-rolling the variant system you already have. Extract a COMPONENT instead — a React component with CVA — which encapsulates behaviour too, not just the class string. `@apply` is legitimate only for markup you do not control (a `.prose` subtree, a third-party widget).

Extract a component when markup repeats, instead of collapsing utilities into a .btn class with @apply

The instinct is understandable: a long `class="inline-flex items-center rounded-md px-4 py-2 …"` looks like duplication, and `.btn { @apply … }` makes it go away. But the duplication was never the problem utility classes had — it was the naming, the indirection, and the coupling. `.btn` brings all three back. You now maintain a name that means nothing to the browser, you cannot read the button's appearance from the button's markup, and any change to `.btn` silently repaints every button in the product, which is exactly the cascade problem Tailwind exists to remove. It also collapses the moment you need a variant: a `.btn-primary`, a `.btn-sm`, a disabled state, and you are hand-rolling the variant system Tailwind already ships. Tailwind's own guidance is a ladder, and `@apply` is not on it: if the repetition is in a list, use a loop; if it is a real UI element, extract a *component* — a React component, with CVA for the variants — because a component encapsulates the markup and the behaviour too, not just the class string. `@apply` earns its place in one narrow case: styling markup you do not control (a `.prose` subtree, a third-party widget) or an unavoidable single element, where a component is not an option. Reach for it there, not to tidy up.

## Rule snippet

```tsx
/* no */ .btn { @apply inline-flex rounded-md px-4 py-2 …; }
// yes: const button = cva("inline-flex rounded-md", { variants: { size: { sm: "px-2 py-1", md: "px-4 py-2" } } });
```

## Bad — do not do this

`layout-no-apply-abstraction-bad`

```tsx
export function NoApplyAbstractionBad() {
  return (
    <div className="w-full max-w-md space-y-4">
      <div className="bg-card border border-border rounded-lg p-4">
        <h4 className="font-medium mb-1">&ldquo;The markup was too long, so I cleaned it up&rdquo;</h4>
        <div className="bg-muted rounded-md p-3 font-mono text-xs overflow-x-auto mt-3">
          <pre className="text-error">{`/* buttons.css */
.btn {
  @apply inline-flex items-center rounded-md px-4 py-2
         text-sm font-medium transition-colors;
}
.btn-primary   { @apply bg-primary text-primary-foreground; }
.btn-secondary { @apply bg-secondary text-secondary-foreground; }
.btn-sm        { @apply px-3 py-1 text-xs; }
.btn-icon      { @apply p-2; }`}</pre>
        </div>
      </div>

      <div className="bg-card border border-border rounded-lg p-4">
        <h5 className="text-sm font-medium mb-1">What you actually bought</h5>
        <div className="bg-muted rounded-md p-3 font-mono text-xs overflow-x-auto mt-3">
          <pre className="text-error">{`<button class="btn btn-primary btn-sm">Save</button>`}</pre>
        </div>
        <ul className="mt-3 text-xs space-y-2">
          <li className="flex items-start gap-2">
            <span className="mt-1.5 w-1.5 h-1.5 shrink-0 rounded-full bg-error" />
            <span>
              A name that means nothing to the browser, and you cannot read the button&rsquo;s appearance from the
              button.
            </span>
          </li>
          <li className="flex items-start gap-2">
            <span className="mt-1.5 w-1.5 h-1.5 shrink-0 rounded-full bg-error" />
            <span>
              Editing <code className="font-mono">.btn</code> silently repaints every button in the product — the exact
              cascade problem utilities exist to remove.
            </span>
          </li>
          <li className="flex items-start gap-2">
            <span className="mt-1.5 w-1.5 h-1.5 shrink-0 rounded-full bg-error" />
            <span>
              Four classes in and you are hand-rolling a variant system. Which wins,{' '}
              <code className="font-mono">btn-sm</code> or <code className="font-mono">btn-icon</code>? Nobody knows.
            </span>
          </li>
          <li className="flex items-start gap-2">
            <span className="mt-1.5 w-1.5 h-1.5 shrink-0 rounded-full bg-error" />
            <span>The markup got shorter. The system got a second styling language.</span>
          </li>
        </ul>
      </div>

      <p className="text-xs text-error">
        @apply moved the utilities into a CSS file and called it an abstraction. It abstracted nothing.
      </p>
    </div>
  );
}
```

## Good — do this

`layout-no-apply-abstraction-good`

```tsx
export function NoApplyAbstractionGood() {
  return (
    <div className="w-full max-w-md space-y-4">
      <div className="bg-card border border-border rounded-lg p-4">
        <h4 className="font-medium mb-1">Extract a component, not a class</h4>
        <div className="bg-muted rounded-md p-3 font-mono text-xs overflow-x-auto mt-3">
          <pre className="text-foreground">{`// button.tsx
const button = cva(
  'inline-flex items-center rounded-md font-medium transition-colors',
  {
    variants: {
      intent: {
        primary: 'bg-primary text-primary-foreground',
        secondary: 'bg-secondary text-secondary-foreground',
      },
      size: { sm: 'px-3 py-1 text-xs', md: 'px-4 py-2 text-sm' },
    },
    defaultVariants: { intent: 'primary', size: 'md' },
  },
);`}</pre>
        </div>
        <div className="bg-muted rounded-md p-3 font-mono text-xs overflow-x-auto mt-2">
          <pre className="text-foreground">{`<Button intent="primary" size="sm">Save</Button>`}</pre>
        </div>
      </div>

      <div className="bg-card border border-border rounded-lg p-4">
        <h5 className="text-sm font-medium mb-3">Rendered</h5>
        <div className="flex flex-wrap items-center gap-2">
          <button className="inline-flex items-center rounded-md bg-primary px-3 py-1 text-xs font-medium text-primary-foreground transition-colors">
            Save
          </button>
          <button className="inline-flex items-center rounded-md bg-secondary px-4 py-2 text-sm font-medium text-secondary-foreground transition-colors">
            Cancel
          </button>
        </div>
        <ul className="mt-3 text-xs space-y-2">
          <li className="flex items-start gap-2">
            <span className="mt-1.5 w-1.5 h-1.5 shrink-0 rounded-full bg-success" />
            <span>Variants are typed. An invalid combination is a compile error, not a mystery.</span>
          </li>
          <li className="flex items-start gap-2">
            <span className="mt-1.5 w-1.5 h-1.5 shrink-0 rounded-full bg-success" />
            <span>
              The component owns behaviour too — the disabled state, the loading spinner, the{' '}
              <code className="font-mono">type=&quot;button&quot;</code> nobody remembers.
            </span>
          </li>
          <li className="flex items-start gap-2">
            <span className="mt-1.5 w-1.5 h-1.5 shrink-0 rounded-full bg-success" />
            <span>One styling language. No CSS file to keep in sync.</span>
          </li>
        </ul>
      </div>

      <p className="text-xs text-muted-foreground">
        The ladder: repeat in a loop &rarr; extract a component &rarr; and only for markup you do not control (a{' '}
        <code className="font-mono">.prose</code> subtree, a third-party widget), reach for{' '}
        <code className="font-mono">@apply</code>.
      </p>
    </div>
  );
}
```

## References

- [Tailwind v4: Managing duplication](https://tailwindcss.com/docs/styling-with-utility-classes)
- [Tailwind v4: Functions and directives](https://tailwindcss.com/docs/functions-and-directives)
