CodeBlock Backtick Bug: Template Literals and Prism.js
Why template literals with backticks break Prism.js syntax highlighting in CodeBlock components, and how to fix it
The Problem
When displaying styled-components code (or any code containing template literal backticks) inside a CodeBlock component, Prism.js gets confused and renders the content in a completely broken layout - with text appearing in wrong positions, lines jumbled, and the first line often appearing at the bottom.
Root Cause
The issue is a collision between:
- JSX template literals (the outer backticks wrapping CodeBlock content)
- Code content that itself contains backticks (like styled-components syntax)
- Prism.js tokenization getting confused by unmatched/nested backticks
When Prism.js parses TypeScript/JavaScript, it treats backticks as template literal delimiters. If the code content has backticks that Prism interprets as opening a template literal, but the structure doesn't match what it expects, the tokenization breaks and the rendered output becomes garbled.
Broken Examples
These patterns will cause rendering issues. Do not use these:
β Template literal with escaped backticks
Here's a live demonstration of the bug. This CodeBlock uses language="typescript" with unicode-escaped backticks (\u0060). Notice how it renders incorrectly:
export const Divider = styled.hr`
border: none;
border-top: 1px solid rgba(255, 255, 255, 0.1);
margin: ${props => props.theme.spacing.md} 0;
`;The content appears jumbled, with lines in wrong positions. This is what the JSX code looks like:
// THIS BREAKS - don't do this
<CodeBlock language="typescript">
{`export const Divider = styled.hr\`
border: none;
\`;`}
</CodeBlock>The \` escape sequence doesn't work inside template literals - it just produces a literal backslash followed by a backtick, which Prism then misparses.
β Unicode escapes for backticks
// THIS ALSO BREAKS
<CodeBlock language="typescript">
{`export const Divider = styled.hr\u0060
border: none;
\u0060;`}
</CodeBlock>Unicode escapes (\u0060) render as actual backticks in the output, which Prism then tries to parse as template literal syntax.
Working Solutions
β Solution 1: Use CSS language, skip the wrapper
If you're showing styled-components styles, just show the CSS part without the styled.hr`...` wrapper. Use language="css" and omit the template literal backticks entirely:
/* Divider component styles */
border: none;
border-top: 1px solid rgba(255, 255, 255, 0.1);
margin: 16px 0;This works because there are no backticks in the content for Prism to misparse.
β Solution 2: Double-quoted string with newlines
Use a regular double-quoted string with explicit \n newlines:
// WORKS - double quotes, backticks are just characters
<CodeBlock language="typescript">
{"export const Divider = styled.hr`\n border: none;\n`;"}
</CodeBlock>In a double-quoted string, backticks are just regular characters - no escaping needed. The downside is readability suffers with \n everywhere.
β Solution 3: Use plaintext language
// WORKS - no syntax highlighting, no parsing issues
<CodeBlock language="plaintext">
{`export const Divider = styled.hr`
border: none;
`;`}
</CodeBlock>If you don't need syntax highlighting, plaintext tells Prism to skip tokenization entirely.
Why This Is Hard to Debug
- The error is visual, not a crash - The page renders, but the text appears scrambled. No error in console.
- It looks like a CSS problem - Text in wrong positions feels like a layout bug, not a parsing bug.
- Escaping seems like it should work -
\`is the standard escape for backticks, but it doesn't help here because the issue is in Prism's parsing, not JSX compilation. - Different approaches cause different failures - Some break Prism rendering, others cause React hydration errors, making it hard to triangulate the root cause.
Wait, Why Do The Examples Above Work?
Sharp-eyed readers may notice something strange: the "broken" examples in this very document display correctly. What gives?
The key is the language prop. All the examples above use language="tsx", not language="typescript".
When Prism parses TSX, it understands JSX syntax. It sees:
<CodeBlock>{`styled.hr`...``}</CodeBlock>...and correctly tokenizes the outer template literal as a JSX expression. The backticks inside are just string content within that expression - they're not being parsed as standalone JavaScript template literal delimiters.
But when you use language="typescript" to show just the styled-components code without the JSX wrapper:
/* This is what you're trying to show */
styled.hr`
border: none;
`;Now Prism sees raw TypeScript with an opening backtick after styled.hr. It tries to parse everything after that as template literal content, but the structure doesn't match what it expects, and the tokenization goes haywire.
The Abstraction Level Matters
- Works: Showing JSX code that contains template literals (use
language="tsx") - Breaks: Showing raw TypeScript/JavaScript with template literals (use
language="typescript"orlanguage="javascript")
It's the difference between showing code that uses code vs showing code directly.
The Lesson
When displaying code that contains template literal syntax inside a syntax-highlighted CodeBlock, avoid putting backticks in the content. Either:
- Show just the relevant parts (CSS without the styled wrapper)
- Use double-quoted strings instead of template literals
- Use
plaintextlanguage to skip highlighting - Wrap the code in JSX and use
language="tsx"(meta-example) - Accept that some code samples just can't be elegantly displayed
This is a fundamental limitation of using Prism.js to highlight code that itself contains the delimiter characters that Prism uses for parsing. The abstraction level determines whether Prism can correctly tokenize the content.