Customization and debugging

Add a custom utility

Create a project-specific utility when a reusable behavior does not belong in the built-in scale or a semantic component class.

When Tailwind has no utility for something you need in several places, you can add your own. In v4 you do it in CSS with @utility:

@utility content-auto {
  content-visibility: auto;
}

@utility content-visible {
  content-visibility: visible;
}

Now both classes exist in your markup, and they work with every variant:

<section class="content-auto lg:content-visible">...</section>

Below lg the browser can skip rendering off-screen content in this section. From lg upward it renders everything. Inspect the section at both widths and you’ll see content-visibility change in the Computed panel.

Why not just write a class?

You could write .content-auto in a normal stylesheet. The difference is where Tailwind puts it. @utility registers the class in the utility layer, so it sorts with the built-in utilities and it responds to hover:, lg:, dark:, and the rest. A random class in a random stylesheet does neither.

When to create one

I create a custom utility when all of these are true:

  • the CSS behavior is useful in several places
  • Tailwind doesn’t already express it clearly
  • the class has one narrow, property-focused meaning
  • I want variants like hover: or lg: to compose with it

Start with an arbitrary property

For a one-off, don’t create a utility. Use an arbitrary property:

<div class="[content-visibility:auto]">...</div>

When the second and third copies show up, promote it to @utility. Repetition is the signal. And if the value belongs to an existing family, like a color, a font, a spacing step, or a breakpoint, a @theme variable is the better extension point. Don’t write @utility bg-brand when --color-brand does it.

A utility is not a component

.button-primary combines layout, color, typography, and states because it represents a reusable interface concept. content-auto represents one CSS behavior. Keep them separate. Hiding a whole button inside a “utility” name confuses everyone who reads the class list later.

Functional utilities

@utility can also accept values, like tab-* reading from a theme namespace. That’s powerful, but it creates a styling API your team has to learn and maintain. Start with a fixed utility. Add a value family only when you have a real range of values to express.

Try this: use one arbitrary property twice, promote it to @utility, and apply a responsive variant. Then explain why it’s a utility rather than a theme token or a component class.

Lesson completed