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

  1. Replace non-declaration children with flat `key: value;` declarations.
  2. Move any CSS rules out of the `@plugin` block into the stylesheet proper.
  3. 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

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


AI-assisted analysis of tailwindlabs/tailwindcss@16e94cbf7f (2026-08-12). Data as JSON: /api/errors/0f3595b3d5a785b3. Report an issue: GitHub.