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 meansView on GitHub (pinned to 16e94cbf7f)
Solutions
- Add the missing `}` for the at-rule named in the message.
- Use brace-matching to locate the unclosed at-rule block.
- Run a CSS formatter to identify the imbalance.
- 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
- Always close @media / @layer / @supports / @keyframes blocks.
- Use brace matching; format CSS regularly.
- For generated CSS, test that every at-rule emits a closer.
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
- Missing closing } at ${parent.selector}
- Invalid custom property, expected a value
- Invalid declaration: `${buffer.trim()}`
- Missing opening {
- Missing opening (
AI-assisted analysis of tailwindlabs/tailwindcss@16e94cbf7f (2026-08-12).
Data as JSON: /api/errors/61c9551007e4e941.
Report an issue: GitHub.