tailwindlabs/tailwindcss · error · CssSyntaxError

Unterminated string: ${input.slice(startIdx, i + 1) + String

Error message

Unterminated string: ${input.slice(startIdx, i + 1) + String.fromCharCode(quoteChar)}

What it means

Thrown by the string scanner when, inside a quoted string, it sees a semicolon whose next character is a line break (or CRLF). That pattern means the string was never closed before the declaration terminator and newline — i.e. an unterminated string that the parser cannot recover gracefully. The message reconstructs the partial string from `startIdx` to the current position and appends the quote character that was expected.

Source

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

      return i
    }

    // End of the line without ending the string but with a `;` at the end.
    //
    // E.g.:
    //
    // ```css
    // .foo {
    //   content: "This is a string with a;
    //                                    ^ Missing "
    // }
    // ```
    else if (
      peekChar === SEMICOLON &&
      (input.charCodeAt(i + 1) === LINE_BREAK ||
        (input.charCodeAt(i + 1) === CARRIAGE_RETURN && input.charCodeAt(i + 2) === LINE_BREAK))
    ) {
      throw new CssSyntaxError(
        `Unterminated string: ${input.slice(startIdx, i + 1) + String.fromCharCode(quoteChar)}`,
        source ? [source, startIdx, i + 1] : null,
      )
    }

    // End of the line without ending the string.
    //
    // E.g.:
    //
    // ```css
    // .foo {
    //   content: "This is a string with a
    //                                    ^ Missing "
    // }
    // ```
    else if (
      peekChar === LINE_BREAK ||
      (peekChar === CARRIAGE_RETURN && input.charCodeAt(i + 1) === LINE_BREAK)

View on GitHub (pinned to 16e94cbf7f)

Solutions

  1. Close the string with the matching quote, e.g. `content: "hello";`.
  2. For multi-line content, escape newlines or use a backslash continuation as CSS allows.
  3. Use single quotes consistently to avoid confusion with apostrophes in the content.

Example fix

/* before */
.x { content: "hello;
}
/* after */
.x { content: "hello"; }
Defensive patterns

Strategy: validation

Validate before calling

// Detect unterminated strings terminated by `;<newline>`
function findUnterminatedStrings(css: string): string[] {
  const bad: string[] = [];
  const re = /(["'])((?:[^"'\n])*);[ \t]*\n/g;
  let m;
  while ((m = re.exec(css))) {
    if (!m[2].includes(m[1])) bad.push(m[0]);
  }
  return bad;
}

Try / catch

try {
  const ast = CSS.parse(input);
} catch (e) {
  if (e instanceof CssSyntaxError && e.message.startsWith('Unterminated string')) {
    // message includes the partial string and expected quote
  } else throw e;
}

Prevention

When it happens

Trigger: Writing `.x { content: "hello; }` where the `;` is inside the quotes but the closing quote is missing, followed by a newline. The scanner treats `"…;<newline>` as evidence the string spilled past the declaration boundary.

Common situations: Forgetting the closing quote in `content:`; multi-line content strings authored without proper escaping; copy-paste that drops the trailing quote.

Related errors


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