Skip to main content
The @utility directive allows you to define custom utility classes that integrate seamlessly with Tailwind CSS. These utilities can be applied using regular class names or with @apply.

Syntax

@utility utility-name {
  /* CSS declarations */
}

Basic Usage

Static Utilities

Create a simple static utility that always generates the same CSS:
@utility center {
  display: flex;
  align-items: center;
  justify-content: center;
}
Use it like any built-in utility:
<div class="center">
  <!-- Content is centered -->
</div>
Or with @apply:
.card {
  @apply center;
}

Functional Utilities

Create utilities that accept a value using the -* suffix:
@utility glow-* {
  box-shadow: 0 0 20px 5px var(--value);
}
The --value variable is automatically replaced with the utility value:
<!-- Generates: box-shadow: 0 0 20px 5px #3b82f6; -->
<div class="glow-[#3b82f6]">
  Glowing element
</div>
You can also use theme values:
@theme {
  --color-primary: #3b82f6;
}

@utility glow-* {
  box-shadow: 0 0 20px 5px var(--value);
}
<!-- Uses theme value --color-primary -->
<div class="glow-primary">
  Glowing element
</div>

Utility Naming Rules

Static Utilities
string
Must be alphanumeric, start with a lowercase letter, and can contain hyphens:
@utility my-utility { }
@utility btn { }
@utility flex-center { }
@utility MyUtility { }  /* uppercase */
@utility _utility { }   /* underscore start */
@utility -utility { }   /* hyphen start */
Functional Utilities
string
Must end with -* and follow the same naming rules:
@utility glow-* { }
@utility custom-spacing-* { }
@utility glow* { }       /* missing hyphen */
@utility *glow { }       /* wrong position */
@utility glow-*-more { } /* * not at end */

Using CSS Variables

The —value Variable

For functional utilities, --value represents the user-provided value:
@utility ring-* {
  box-shadow: 0 0 0 var(--value) rgb(59 130 246 / 0.5);
}
<!-- Generates: box-shadow: 0 0 0 3px rgb(59 130 246 / 0.5); -->
<div class="ring-[3px]"></div>

Referencing Theme Variables

You can reference theme variables using var():
@theme {
  --color-brand: #ff6b6b;
  --radius: 0.5rem;
}

@utility card {
  background: var(--color-brand);
  border-radius: var(--radius);
  padding: 1rem;
}

Nested Rules

Utilities can contain nested rules using the & selector:
@utility btn {
  padding: 0.5rem 1rem;
  border-radius: 0.25rem;

  &:hover {
    opacity: 0.8;
  }

  &:active {
    transform: scale(0.95);
  }
}
Generated HTML class:
<button class="btn">Click me</button>
Generated CSS:
.btn {
  padding: 0.5rem 1rem;
  border-radius: 0.25rem;
}

.btn:hover {
  opacity: 0.8;
}

.btn:active {
  transform: scale(0.95);
}

Pseudo-elements

Create utilities with pseudo-elements:
@utility tooltip {
  position: relative;

  &::before {
    content: attr(data-tooltip);
    position: absolute;
    bottom: 100%;
    left: 50%;
    transform: translateX(-50%);
    padding: 0.25rem 0.5rem;
    background: black;
    color: white;
    border-radius: 0.25rem;
    font-size: 0.875rem;
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
  }

  &:hover::before {
    opacity: 1;
  }
}

Using @apply in Utilities

You can use @apply within custom utilities:
@utility my-flex {
  @apply flex items-center justify-center;
}

@utility card {
  @apply my-flex;
  padding: 1rem;
  border-radius: 0.5rem;
}
Utilities defined with @utility are available to @apply even before they are defined in the source order.

Recursive Application

Utilities can recursively apply other custom utilities:
@utility a {
  @apply b;
}

@utility b {
  @apply focus:c;
}

@utility c {
  @apply flex!;
}

At-Rules and Media Queries

Include media queries and other at-rules:
@utility responsive-text {
  font-size: 1rem;

  @media (min-width: 768px) {
    font-size: 1.25rem;
  }

  @media (min-width: 1024px) {
    font-size: 1.5rem;
  }
}

Restrictions

The @utility directive must be used at the top level and cannot be nested inside other rules.
/* ❌ Invalid - nested utility */
.container {
  @utility my-utility {
    color: red;
  }
}

/* ✅ Valid - top-level utility */
@utility my-utility {
  color: red;
}
Custom utilities must include at least one CSS property. Empty utilities are not allowed.
/* ❌ Invalid - empty utility */
@utility empty {
}

/* ✅ Valid - has properties */
@utility valid {
  display: block;
}

Parsing and Processing

When you define a utility with @utility, the parser:
  1. Validates the utility name according to naming rules
  2. Checks for the -* suffix to determine if it’s functional
  3. Extracts the CSS declarations from the body
  4. Registers the utility in the design system
  5. Makes it available to @apply and class names
  6. Removes the @utility rule from the final CSS
From the source code (index.ts:222-254):
if (node.name === '@utility') {
  if (ctx.parent !== null) {
    throw new Error('`@utility` cannot be nested.');
  }

  if (node.nodes.length === 0) {
    throw new Error(
      `@utility ${node.params} is empty. Utilities should include at least one property.`
    );
  }

  let utility = createCssUtility(node);
  if (utility === null) {
    // Validation errors for invalid utility names
  }

  customUtilities.push(utility);
}

Output

The @utility directive itself does not appear in the final CSS. Only the generated utility classes are output when used:
/* Input */
@utility center {
  display: flex;
  align-items: center;
}

@tailwind utilities;
<!-- Used in HTML -->
<div class="center"></div>
/* Output */
.center {
  display: flex;
  align-items: center;
}

Build docs developers (and LLMs) love