tailwindlabs/tailwindcss · error · CssSyntaxError

Missing closing } at ${parent.name} ${parent.params}

Error message

Missing closing } at ${parent.name} ${parent.params}

What it means

Sibling of error 55, thrown at end-of-input when the unclosed parent node is an at-rule (`@media`, `@layer`, `@supports`, etc.) instead of a style rule. The bracket stack is non-empty at EOF and `parent.kind === 'at-rule'`. The message embeds both the at-rule name and its params (e.g. `@media (min-width: 100px)`) and the source position comes from the at-rule's `src`.

Source

Thrown at packages/tailwindcss/src/css-parser.ts:582

      node.src = [source, bufferStart, input.length]
      node.dst = [source, bufferStart, input.length]
    }

    ast.push(node)
  }

  // When we are done parsing then everything should be balanced. If we still
  // have a leftover `parent`, then it means that we have an unterminated block.
  if (closingBracketStack.length > 0 && parent) {
    if (parent.kind === 'rule') {
      throw new CssSyntaxError(
        `Missing closing } at ${parent.selector}`,
        parent.src ? [parent.src[0], parent.src[1], parent.src[1]] : null,
      )
    }

    if (parent.kind === 'at-rule') {
      throw new CssSyntaxError(
        `Missing closing } at ${parent.name} ${parent.params}`,
        parent.src ? [parent.src[0], parent.src[1], parent.src[1]] : null,
      )
    }
  }

  if (licenseComments.length > 0) {
    return (licenseComments as AstNode[]).concat(ast)
  }

  return ast
}

export function parseAtRule(buffer: string, nodes: AstNode[] = []): AtRule {
  let name = buffer
  let params = ''

  // Assumption: The smallest at-rule in CSS right now is `@page`, this means

View on GitHub (pinned to 16e94cbf7f)

Solutions

  1. Add the missing `}` for the at-rule named in the message.
  2. Use brace-matching to locate the unclosed at-rule block.
  3. Run a CSS formatter to identify the imbalance.
  4. If generated, ensure the template always closes at-rule blocks.

Example fix

/* before */
@media (min-width: 100px) {
  .x { color: red; }
/* after */
@media (min-width: 100px) {
  .x { color: red; }
}
Defensive patterns

Strategy: validation

Validate before calling

// Reuse the brace-balance check; the message names the at-rule
function bracesBalanced(css: string): boolean {
  let depth = 0;
  for (const ch of css) {
    if (ch === '{') depth++;
    else if (ch === '}') depth--;
    if (depth < 0) return false;
  }
  return depth === 0;
}

Try / catch

try {
  const ast = CSS.parse(input);
} catch (e) {
  if (e instanceof CssSyntaxError && e.message.startsWith('Missing closing }')) {
    // message includes `@media ...` — close that block
  } else throw e;
}

Prevention

When it happens

Trigger: An `@media`, `@layer`, `@supports`, `@container`, or `@keyframes` block missing its closing `}` at EOF; nested at-rules where an inner block was left open; truncated file.

Common situations: Author forgot to close a media query; large files where a block closer was accidentally deleted; generated CSS missing the closer.

Related errors


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