tailwindlabs/tailwindcss · error · Error
Unexpected `@plugin` option: ${toCss([decl])} `@plugin` op
Error message
Unexpected `@plugin` option:
${toCss([decl])}
`@plugin` options must be a flat list of declarations. What it means
Thrown while parsing the body of an `@plugin` at-rule when one of its child nodes is not a `declaration`. `@plugin` options in CSS must be a flat list of declarations (key: value;), so nested at-rules, rules, or comments-as-nodes are rejected. The offending node is rendered via `toCss` for context.
Source
Thrown at packages/tailwindcss/src/compat/apply-compat-hooks.ts:73
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
// Parse the declaration value as a primitive type
// These are the same primitive values supported by JSON
let value: CssPluginOptions[keyof CssPluginOptions] = decl.value
let parts = segment(value, ',').map((part) => {
part = part.trim()
if (part === 'null') {
return null
} else if (part === 'true') {
return true
} else if (part === 'false') {View on GitHub (pinned to 16e94cbf7f)
Solutions
- Replace non-declaration children with flat `key: value;` declarations.
- Move any CSS rules out of the `@plugin` block into the stylesheet proper.
- If you need complex plugin config, use a JS config file instead of `@plugin` options.
Example fix
// before
@plugin "./typography.js" {
.prose { color: red; }
}
// after
@plugin "./typography.js" {
className: prose;
} Defensive patterns
Strategy: validation
Validate before calling
function assertPluginOptionsFlat(node) {
for (const child of node.nodes ?? []) {
if (child.kind !== 'declaration') {
throw new Error(`@plugin option must be a declaration, got ${child.kind}`);
}
}
} Prevention
- Only use `key: value;` declarations inside `@plugin { }`.
- Move real CSS rules outside the `@plugin` block.
When it happens
Trigger: Writing `@plugin "x.js" { .foo { color: red } }` (a nested rule instead of a declaration) or nesting an `@media` inside `@plugin`. Fires for every child whose `kind !== 'declaration'`.
Common situations: Confusing `@plugin` option syntax with regular CSS nesting; copy-pasting a rule block where options should go.
Related errors
- `@plugin` cannot be nested.
- `@plugin` must have a path.
- `@config` cannot have a body.
- The rule `@apply ${list}` must not have a body.
- `layer(…)` in an `@import` should come before any other func
AI-assisted analysis of tailwindlabs/tailwindcss@16e94cbf7f (2026-08-12).
Data as JSON: /api/errors/0f3595b3d5a785b3.
Report an issue: GitHub.