{"record":{"id":"9eeac38551f243b6","repo":"tailwindlabs/tailwindcss","slug":"the-pattern-pattern-is-not-balanced","errorCode":null,"errorMessage":"The pattern `${pattern}` is not balanced.","messagePattern":"The pattern `(.+?)` is not balanced\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/tailwindcss/src/utils/brace-expansion.ts","lineNumber":30,"sourceCode":"\n  // Find the matching closing brace\n  let depth = 0\n  let endIndex = rest.lastIndexOf('}')\n  for (let i = 0; i < rest.length; i++) {\n    let char = rest[i]\n    if (char === '{') {\n      depth++\n    } else if (char === '}') {\n      depth--\n      if (depth === 0) {\n        endIndex = i\n        break\n      }\n    }\n  }\n\n  if (endIndex === -1) {\n    throw new Error(`The pattern \\`${pattern}\\` is not balanced.`)\n  }\n\n  let inside = rest.slice(1, endIndex)\n  let post = rest.slice(endIndex + 1)\n  let parts: string[]\n\n  if (isSequence(inside)) {\n    parts = expandSequence(inside)\n  } else {\n    parts = segment(inside, ',')\n  }\n\n  parts = parts.flatMap((part) => expand(part))\n\n  let expandedTail = expand(post)\n\n  for (let tail of expandedTail) {\n    for (let part of parts) {","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/tailwindlabs/tailwindcss/blob/16e94cbf7f965c5ad697e90e940b5e178efad67c/packages/tailwindcss/src/utils/brace-expansion.ts#L12-L48","documentation":"Thrown by expand() in brace-expansion.ts when a '{' character has no matching closing '}'. The parser walks the pattern tracking nesting depth; if it never returns to depth 0, endIndex stays -1 and the pattern is rejected as unbalanced. This mirrors bash brace-expansion semantics where every opened brace must close.","triggerScenarios":"Calling expand() with a pattern containing an unmatched '{', e.g. 'img-{icon' or 'a{b,c'. Internally this is used by Tailwind's arbitrary value and variant parsing, so malformed arbitrary values like 'bg-{red' in class names can surface here.","commonSituations":"Typos in utility class names with brace syntax. Dynamically generated class strings where a template literal leaves a dangling brace. User input fed into a brace-expansion-based path/glob utility without sanitization.","solutions":["Balance the braces: add the missing '}' (e.g. 'img-{icon' -> 'img-{icon}').","If a literal '{' is intended, escape or avoid the expansion utility for that input.","Validate the pattern before calling expand(): count '{' and '}' and ensure they match."],"exampleFix":"// before — throws\nlet parts = expand('page-{1..5')\n\n// after\nlet parts = expand('page-{1..5}')","handlingStrategy":"validation","validationCode":"function isBalanced(pattern: string): boolean {\n  let depth = 0\n  for (const ch of pattern) {\n    if (ch === '{') depth++\n    else if (ch === '}') depth--\n    if (depth < 0) return false\n  }\n  return depth === 0\n}\nif (!isBalanced(pattern)) {\n  throw new Error(`Unbalanced braces in: ${pattern}`)\n}\nexpand(pattern)","typeGuard":null,"tryCatchPattern":"try {\n  const parts = expand(pattern)\n} catch (e) {\n  if (e instanceof Error && /not balanced/.test(e.message)) {\n    // fall back to literal pattern\n    return [pattern]\n  }\n  throw e\n}","preventionTips":["Sanitize user/dynamic input before passing to brace expansion.","Count braces when building patterns from templates.","Wrap expand() calls in try/catch with a literal fallback for untrusted input."],"tags":["tailwindcss","brace-expansion","parsing","validation"],"backgroundTag":null,"analyzedSha":"16e94cbf7f965c5ad697e90e940b5e178efad67c","analyzedAt":"2026-08-12T06:02:42.469Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}