tailwindlabs/tailwindcss · error · Error
Exceeded maximum recursion depth while resolving `${uri}` in
Error message
Exceeded maximum recursion depth while resolving `${uri}` in `${base}`) What it means
Thrown by `substituteAtImports` when `recurseCount > 100`. Tailwind does not fully resolve import paths, so it cannot reliably detect true cycles; instead it caps `@import` recursion depth. Hitting the cap almost always indicates a circular import chain (A imports B imports A).
Source
Thrown at packages/tailwindcss/src/at-import.ts:54
let { uri, layer, media, supports } = parsed
// Skip importing data or remote URIs
if (uri.startsWith('data:')) return
if (uri.startsWith('http://') || uri.startsWith('https://')) return
let contextNode = context({}, [])
promises.push(
(async () => {
// Since we do not have fully resolved paths in core, we can't
// reliably detect circular imports. Instead, we try to limit the
// recursion depth to a number that is too large to be reached in
// practice.
if (recurseCount > 100) {
throw new Error(
`Exceeded maximum recursion depth while resolving \`${uri}\` in \`${base}\`)`,
)
}
let loaded = await loadStylesheet(uri, base)
let ast = CSS.parse(loaded.content, { from: track ? loaded.path : undefined })
await substituteAtImports(ast, loaded.base, loadStylesheet, recurseCount + 1, track)
contextNode.nodes = buildImportNodes(
node,
[context({ base: loaded.base }, ast)],
layer,
media,
supports,
)
})(),
)
// The resolved Stylesheets already have their transitive @imports
// resolved, so we can skip walking them.
return WalkAction.ReplaceSkip(contextNode)View on GitHub (pinned to 16e94cbf7f)
Solutions
- Inspect the `uri` and `base` in the message and trace the import chain back to the file that re-imports an ancestor.
- Break the cycle by inlining the shared CSS into a third file both import once.
- Remove redundant `@import` lines introduced during a merge or refactor.
Example fix
// before // a.css @import "b.css"; // b.css @import "a.css"; /* circular */ // after // shared.css holds the common rules; a.css and b.css both import only shared.css
Defensive patterns
Strategy: validation
Validate before calling
function detectImportCycle(entry, load, seen = new Set()) {
if (seen.has(entry)) throw new Error(`Circular @import involving ${entry}`);
seen.add(entry);
for (const imp of extractImports(load(entry))) detectImportCycle(imp, load, new Set(seen));
} Prevention
- Run a dependency-graph check (e.g. madge) on CSS files to catch cycles before build.
- Prefer a single shared `@import` tree over mutual imports between feature files.
When it happens
Trigger: Two stylesheets that `@import` each other, or a self-importing file; a long legitimate chain deeper than 100 (extremely rare). The guard fires before attempting to `loadStylesheet` for the current `uri`.
Common situations: Refactor that moved `@import` statements between files and accidentally created a cycle; generated CSS that emits imports programmatically; broken relative paths that resolve back to the parent.
Related errors
- `layer(…)` in an `@import` should come before any other func
- The rule `@apply ${list}` must not have a body.
- Cannot apply unprefixed utility class `${candidate}`. Did yo
- Cannot apply utility class `${candidate}` because it has bee
- Cannot apply utility class `${candidate}` because the `${unk
AI-assisted analysis of tailwindlabs/tailwindcss@16e94cbf7f (2026-08-12).
Data as JSON: /api/errors/fab287ce23e5114e.
Report an issue: GitHub.