tailwindlabs/tailwindcss · error · Error

The rule `@apply ${list}` must not have a body.

Error message

The rule `@apply ${list}` must not have a body.

What it means

Thrown when an `@apply` at-rule that references normal utility classes also contains a declaration body (i.e. `child.nodes.length > 0`). Tailwind v4 treats `@apply` as a pure substitution: it expands into the utility's CSS and cannot also hold its own declarations, so a body is rejected. The error lists the offending utility idents so the offending rule is identifiable.

Source

Thrown at packages/tailwindcss/src/apply.ts:209

        // If we find a dashed ident *here* it means that someone is trying
        // to use mixins and our `@apply` behavior together.
        //
        // This is invalid and the rules must be written separately. Let the
        // user know they need to move them into a separate rule.
        let list = dashedIdents.join(' ')

        throw new Error(
          `You cannot use \`@apply\` with both mixins and utilities. Please move \`@apply ${list}\` into a separate rule.`,
        )
      }

      let hasBody = child.nodes.length > 0

      if (hasBody && normalIdents.length) {
        let list = normalIdents.join(' ')

        throw new Error(`The rule \`@apply ${list}\` must not have a body.`)
      }

      // Replace the `@apply` rule with the actual utility classes
      {
        // Parse the candidates to an AST that we can replace the `@apply` rule
        // with.
        let candidates = Object.keys(candidateOffsets)
        let compiled = compileCandidates(candidates, designSystem, {
          respectImportant: false,
          onInvalidCandidate: (candidate) => {
            // When using prefix, make sure prefix is used in candidate
            if (designSystem.theme.prefix && !candidate.startsWith(designSystem.theme.prefix)) {
              throw new Error(
                `Cannot apply unprefixed utility class \`${candidate}\`. Did you mean \`${designSystem.theme.prefix}:${candidate}\`?`,
              )
            }

            // When the utility is blocklisted, let the user know

View on GitHub (pinned to 16e94cbf7f)

Solutions

  1. Remove the body from the `@apply` rule and move those declarations into a sibling rule or wrap the `@apply` in an outer rule that also holds them.
  2. If you need media-query scoping, put `@apply` inside a `@media` block instead of putting the media query inside `@apply`.
  3. If you intended a CSS-mixin `@apply --foo;`, ensure it is the only content of the rule (dashed idents alone skip this check).

Example fix

// before
.btn {
  @apply px-4 py-2 {
    color: red;
  }
}
// after
.btn {
  @apply px-4 py-2;
  color: red;
}
Defensive patterns

Strategy: validation

Validate before calling

// Before writing CSS, ensure no @apply with utility idents has a body.
function assertApplyHasNoBody(css) {
  const re = /@apply\s+[^;{}]+\s*\{[^}]*\}/g;
  const hits = css.match(re) ?? [];
  const bad = hits.filter(h => !/^[\s\S]*--/.test(h.split('@apply')[1]));
  if (bad.length) throw new Error('Move declarations out of @apply body: ' + bad.join('\n'));
}
// assertApplyHasNoBody(myCss);

Prevention

When it happens

Trigger: Writing CSS like `@apply flex { color: red; }` — an `@apply` whose params are normal utility idents (not `--dashed` mixins) AND whose node list is non-empty. Only fires when `hasBody && normalIdents.length` is truthy after the dashed-ident/mixin check has passed.

Common situations: Migrating from v3 where `@apply` accepted trailing declarations; muscle-memory of writing `@apply p-4 { @media (min-width: 768px) { ... } }`; copy-pasting nested at-rules inside an `@apply` block.

Related errors


AI-assisted analysis of tailwindlabs/tailwindcss@16e94cbf7f (2026-08-12). Data as JSON: /api/errors/25a3e7b324339c7b. Report an issue: GitHub.