tailwindlabs/tailwindcss · error · Error

Circular dependency detected: ${toCss([node])} Relies on:

Error message

Circular dependency detected:

${toCss([node])}
Relies on:

${toCss([next])}

What it means

After collecting all `@utility`/`@apply` dependencies, `substituteAtApply` performs a topological visit. If it detects a cycle it cannot attribute to a specific self-reference (the targeted check in error 17), it throws a generic fallback that renders the two CSS nodes involved (`node` and `next`) via `toCss`. This is the catch-all for multi-hop cycles like A→B→A.

Source

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

                case 'functional':
                  if (next.params.replace(/-\*$/, '') === candidateAstNode.root) {
                    throw new Error(
                      `You cannot \`@apply\` the \`${candidate}\` utility here because it creates a circular dependency.`,
                    )
                  }
                  break

                default:
                  candidateAstNode satisfies never
              }
            }
          }
        })
      }

      // Generic fallback error in case we cannot properly detect the origin of
      // the circular dependency.
      throw new Error(
        `Circular dependency detected:\n\n${toCss([node])}\nRelies on:\n\n${toCss([next])}`,
      )
    }

    wip.add(node)

    for (let dependencyId of dependencies.get(node)) {
      for (let dependency of definitions.get(dependencyId)) {
        path.push(node)
        visit(dependency, path)
        path.pop()
      }
    }

    seen.add(node)
    wip.delete(node)

    sorted.push(node)

View on GitHub (pinned to 16e94cbf7f)

Solutions

  1. Break the cycle: remove or redirect one of the `@apply` references so the graph becomes acyclic.
  2. Extract the shared styles into a third utility that both reference in one direction only.
  3. Re-run the build; the error names the two CSS blocks to pinpoint the edge to cut.

Example fix

/* before */
@utility a { @apply b; color: red; }
@utility b { @apply a; color: blue; }

/* after */
@utility base { color: red; }
@utility a { @apply base; }
@utility b { @apply a; color: blue; }
Defensive patterns

Strategy: validation

Validate before calling

// Build a utility dependency graph and detect cycles before build
type DepGraph = Map<string, Set<string>>
function detectApplyCycle(graph: DepGraph): string[] | null {
  const WHITE=0, GRAY=1, BLACK=2
  const color = new Map<string, number>()
  let cycle: string[] | null = null
  for (const [n] of graph) {
    color.set(n, WHITE)
  }
  const visit = (n: string, stack: string[]): boolean => {
    color.set(n, GRAY); stack.push(n)
    for (const dep of graph.get(n) ?? []) {
      if (color.get(dep) === GRAY) { cycle = [...stack.slice(stack.indexOf(dep)), dep]; return true }
      if (color.get(dep) === WHITE && visit(dep, stack)) return true
    }
    stack.pop(); color.set(n, BLACK); return false
  }
  for (const [n] of graph) if (color.get(n) === WHITE && visit(n, [])) break
  return cycle
}

Try / catch

try {
  substituteAtApply(ast, designSystem)
} catch (e) {
  if (/Circular dependency detected/.test((e as Error).message)) {
    // inspect the two CSS blocks in the message, cut one @apply edge
  }
  throw e
}

Prevention

When it happens

Trigger: Two or more utilities that mutually `@apply` each other: `@utility a { @apply b; }` plus `@utility b { @apply a; }`. The DFS `visit` detects a node already in the current `path`/`wip` set and throws at apply.ts:131, showing the CSS of both endpoints.

Common situations: Refactoring utilities into a dependency chain that accidentally loops back, or composing multiple `@utility` definitions that each pull in the other.

Related errors


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