tailwindlabs/tailwindcss · error · Error
`@plugin` cannot be nested.
Error message
`@plugin` cannot be nested.
What it means
Thrown by the v3-compat hook when an `@plugin` at-rule is encountered with a non-null `ctx.parent`, i.e. it is nested inside another rule or at-rule. `@plugin` must be top-level so Tailwind can resolve and load the module at the right phase.
Source
Thrown at packages/tailwindcss/src/compat/apply-compat-hooks.ts:61
let pluginPaths: [
{ id: string; base: string; reference: boolean; src: SourceLocation | undefined },
CssPluginOptions | null,
][] = []
let configPaths: {
id: string
base: string
reference: boolean
src: SourceLocation | undefined
}[] = []
walk(ast, (node, _ctx) => {
if (node.kind !== 'at-rule') return
let ctx = cssContext(_ctx)
// Collect paths from `@plugin` at-rules
if (node.name === '@plugin') {
if (ctx.parent !== null) {
throw new Error('`@plugin` cannot be nested.')
}
let pluginPath = node.params.slice(1, -1)
if (pluginPath.length === 0) {
throw new Error('`@plugin` must have a path.')
}
let options: CssPluginOptions = {}
for (let decl of node.nodes ?? []) {
if (decl.kind !== 'declaration') {
throw new Error(
`Unexpected \`@plugin\` option:\n\n${toCss([decl])}\n\n\`@plugin\` options must be a flat list of declarations.`,
)
}
if (decl.value === undefined) continue
View on GitHub (pinned to 16e94cbf7f)
Solutions
- Move the `@plugin` at-rule to the top level of the stylesheet, outside any block.
- Remove any surrounding `@layer`/`@media`/selector wrapper around the `@plugin`.
Example fix
// before
@layer base {
@plugin "./my-plugin.js";
}
// after
@plugin "./my-plugin.js"; Defensive patterns
Strategy: validation
Validate before calling
function assertPluginAtTopLevel(ast) {
walk(ast, (node, ctx) => {
if (node.kind === 'at-rule' && node.name === '@plugin' && ctx.parent !== null) {
throw new Error('@plugin must be top-level');
}
});
} Prevention
- Place all `@plugin` and `@config` at-rules at the very top of your entry CSS file.
- Review diff indentation when merging CSS to catch accidental nesting.
When it happens
Trigger: Writing `@plugin "./my-plugin.js";` inside a `@layer`, `@media`, or any selector rule. The walker sets `ctx.parent` for nested nodes, and the guard rejects it.
Common situations: Indenting `@plugin` under a rule by accident; converting a JS config to CSS `@plugin` and leaving it scoped; editor auto-indent pushing it inside a block.
Related errors
- `@plugin` must have a path.
- Unexpected `@plugin` option: ${toCss([decl])} `@plugin` op
- `@config` cannot be nested.
- Unexpected `@plugin` option: Value of declaration `${toCss([
- `@config` cannot have a body.
AI-assisted analysis of tailwindlabs/tailwindcss@16e94cbf7f (2026-08-12).
Data as JSON: /api/errors/9d91840d6ce3d87d.
Report an issue: GitHub.