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
- Close the string with the matching quote, e.g. `content: "hello";`.
- For multi-line content, escape newlines or use a backslash continuation as CSS allows.
- 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
- Always close quoted strings with the matching quote.
- Use a CSS-aware editor that flags unterminated strings.
- For content: values, prefer single quotes if the text contains apostrophes.
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
- Unterminated string: ${input.slice(startIdx, i) + String.fro
- 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/76be887420b95e63.
Report an issue: GitHub.