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
- Break the cycle: remove or redirect one of the `@apply` references so the graph becomes acyclic.
- Extract the shared styles into a third utility that both reference in one direction only.
- 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
- Keep the @utility/@apply graph acyclic; extract shared base utilities.
- Before building, lint utility dependencies for cycles.
- Read the two CSS nodes in the error message to find the edge to cut.
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
- You cannot `@apply` the `${candidate}` utility here because
- You cannot use `@apply` inside `@keyframes`.
- You cannot use `@apply` with both mixins and utilities. Plea
- The rule `@apply ${list}` must not have a body.
- Cannot apply unprefixed utility class `${candidate}`. Did yo
AI-assisted analysis of tailwindlabs/tailwindcss@16e94cbf7f (2026-08-12).
Data as JSON: /api/errors/4c361d9bbb115a0c.
Report an issue: GitHub.