tailwindlabs/tailwindcss · error · Error
Circular dependency detected in custom variants: ${output}
Error message
Circular dependency detected in custom variants:
${output} What it means
Thrown by `topologicalSort` when `@custom-variant` rules reference each other via `@variant` in a way that forms a cycle (e.g. variant A references B, B references C, C references A). The error message renders the cycle as CSS with a `/* ← */` marker on the starting node so the loop is visible. Tailwind refuses to register variants in an undefined order.
Source
Thrown at packages/tailwindcss/src/index.ts:644
for (let name of customVariants.keys()) {
// Pre-register the variant to ensure its position in the variant list is
// based on the order we see them in the CSS.
designSystem.variants.static(name, () => {})
}
// Register custom variants in order
for (let variant of topologicalSort(customVariantDependencies, {
onCircularDependency(path, start) {
let output = toCss(
path.map((name, idx) => {
return atRule('@custom-variant', name, [atRule('@variant', path[idx + 1] ?? start, [])])
}),
)
.replaceAll(';', ' { … }')
.replace(`@custom-variant ${start} {`, `@custom-variant ${start} { /* ← */`)
throw new Error(`Circular dependency detected in custom variants:\n\n${output}`)
},
})) {
customVariants.get(variant)?.(designSystem)
}
for (let customUtility of customUtilities) {
customUtility(designSystem)
}
// Output final set of theme variables at the position of the first
// `@theme` rule.
if (firstThemeRule) {
let nodes = []
for (let [key, value] of designSystem.theme.entries()) {
if (value.options & ThemeOptions.REFERENCE) continue
let node = decl(escape(key), value.value)
node.src = value.srcView on GitHub (pinned to 16e94cbf7f)
Solutions
- Break the cycle: identify the back-edge from the error's rendered output and remove or retarget one `@variant` reference.
- If two variants are truly equivalent, alias them in one direction only (`@custom-variant a { @variant b; }` and stop).
- Flatten mutually-dependent variants into a single `@custom-variant` that lists all selectors directly.
Example fix
/* before */
@custom-variant a { @variant b; }
@custom-variant b { @variant a; }
/* after */
@custom-variant a (&:hover);
@custom-variant b { @variant a; } Defensive patterns
Strategy: validation
Validate before calling
// Detect cycles in @custom-variant dependencies before compiling.
import postcss from 'postcss'
function detectCustomVariantCycles(css: string): string[][] {
const graph = new Map<string, Set<string>>()
postcss.parse(css).walkAtRules('@custom-variant', (rule) => {
const name = rule.params.split(' ')[0]
const deps = new Set<string>()
rule.walkAtRules('@variant', (v) => deps.add(v.params.split(' ')[0]))
graph.set(name, deps)
})
const cycles: string[][] = []
const visited = new Set<string>()
const stack: string[] = []
function dfs(node: string) {
if (stack.includes(node)) {
cycles.push(stack.slice(stack.indexOf(node)).concat(node))
return
}
if (visited.has(node)) return
visited.add(node)
stack.push(node)
for (const dep of graph.get(node) ?? []) dfs(dep)
stack.pop()
}
for (const node of graph.keys()) dfs(node)
return cycles
} Prevention
- Keep `@custom-variant` references acyclic — alias in one direction only.
- When renaming a variant, grep for `@variant <old-name>` and update every reference to avoid closing a loop.
- Run a cycle-detection pass in CI for `@custom-variant` rules.
When it happens
Trigger: Defining `@custom-variant a { @variant b; }`, `@custom-variant b { @variant a; }` — or any longer cycle across `@custom-variant` body-form rules.
Common situations: Refactoring variants and accidentally introducing mutual references; renaming a variant but forgetting to update a reference that closes a loop.
Related errors
- You cannot `@apply` the `${candidate}` utility here because
- Circular dependency detected: ${toCss([node])} Relies on:
- `@custom-variant` cannot be nested.
- `@custom-variant ${name}` defines an invalid variant name. V
- `@custom-variant ${name}` cannot have both a selector and a
AI-assisted analysis of tailwindlabs/tailwindcss@16e94cbf7f (2026-08-12).
Data as JSON: /api/errors/86801d8c89b32ade.
Report an issue: GitHub.