# Create Custom Utilities with @utility

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

> SHOULD: Create custom utilities in @layer utilities for missing CSS properties (text-wrap, scrollbar)

Register project-specific utilities with the @utility directive, the only form that supports variants

Tailwind does not ship every CSS property — `scrollbar-width`, `text-wrap: balance` on an older target, `mask-type` — so you will need to add your own. In v4 there is exactly one correct way, and it is `@utility name { … }`. This is not a stylistic preference over the old `@layer utilities { .name { … } }` form: that form no longer registers anything with the compiler, so `md:scrollbar-hide` and `hover:scrollbar-hide` simply do not exist and quietly do nothing at the call site. `@utility` puts the name in Tailwind's registry, which buys three things a plain class cannot have: every variant works, the utility is sorted by property count alongside the built-ins so overriding it behaves predictably, and it is only emitted when actually used. Two details worth knowing: `@utility` requires a top-level, unnested rule (no `&` selectors, no nesting — that is what makes it variant-composable), and a trailing `-*` makes it take a value, so `@utility tab-*` plus `--tab-size-*` theme tokens gives you `tab-2`, `tab-4`, and `tab-[13]`.

## Bad — do not do this

`layout-custom-utilities-bad`

```tsx
export function CustomUtilitiesBad() {
  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">Defining it in @layer utilities</h4>
        <p className="text-xs text-muted-foreground mb-3">
          The v3 shape. In v4 this registers nothing with the compiler.
        </p>
        <div className="bg-muted rounded-md p-3 font-mono text-xs overflow-x-auto">
          <pre className="text-error">{`@layer utilities {
  .paused {
    animation-play-state: paused;
  }
}`}</pre>
        </div>
      </div>

      <div className="bg-card border border-border rounded-lg p-4">
        <h5 className="text-sm font-medium mb-1">Then using it like a utility</h5>
        <div className="bg-muted rounded-md p-3 font-mono text-xs overflow-x-auto mt-3">
          <pre className="text-error">{`<div class="animate-spin hover:paused">
<!-- hover:paused was never generated -->

<div class="motion-reduce:paused">
<!-- neither was this -->`}</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>
              Bare <code className="font-mono">paused</code> works, so the bug hides: it looks like the utility exists.
            </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 variants are silently dropped. The spinner never stops on hover.</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>You debug the animation for an hour before suspecting the class.</span>
          </li>
        </ul>
      </div>

      <p className="text-xs text-error">
        In v4, @layer utilities is not an alternative to @utility. It is the wrong answer.
      </p>
    </div>
  );
}
```

## Good — do this

`layout-custom-utilities-good`

```tsx
export function CustomUtilitiesGood() {
  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">Defining it with @utility</h4>
        <p className="text-xs text-muted-foreground mb-3">
          Top-level, unnested — that is what makes it variant-composable.
        </p>
        <div className="bg-muted rounded-md p-3 font-mono text-xs overflow-x-auto">
          <pre className="text-foreground">{`@utility paused {
  animation-play-state: paused;
}`}</pre>
        </div>
      </div>

      <div className="bg-card border border-border rounded-lg p-4">
        <h5 className="text-sm font-medium mb-1">Every variant now works</h5>
        <div className="bg-muted rounded-md p-3 font-mono text-xs overflow-x-auto mt-3">
          <pre className="text-foreground">{`<div class="animate-spin hover:paused">
<div class="animate-spin motion-reduce:paused">`}</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-success" />
            <span>The name is in Tailwind&rsquo;s registry, so every variant is generated on demand.</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>Sorted by property count alongside the built-ins, so overriding it is predictable.</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>Only emitted when actually used.</span>
          </li>
        </ul>
      </div>

      <div className="bg-card border border-border rounded-lg p-4">
        <h5 className="text-sm font-medium mb-1">A trailing -* makes it take a value</h5>
        <div className="bg-muted rounded-md p-3 font-mono text-xs overflow-x-auto mt-3">
          <pre className="text-foreground">{`@utility tab-* {
  tab-size: --value(integer);
}

<pre class="tab-4 md:tab-2">`}</pre>
        </div>
      </div>

      <p className="text-xs text-success">
        @utility is the only form that registers a name. Everything else is a class that looks like one.
      </p>
    </div>
  );
}
```

## References

- [Tailwind v4: Adding custom utilities](https://tailwindcss.com/docs/adding-custom-styles)
- [Tailwind v4: @utility directive](https://tailwindcss.com/docs/functions-and-directives)
