Technical documentation and developer-facing applications frequently need to display code snippets with syntax highlighting. Raw <pre><code> blocks are functional but visually flat—they lack the color cues that developers rely on to scan code quickly. Syntax highlighting transforms source code into color-coded tokens that distinguish keywords, strings, comments, operators, and identifiers at a glance, dramatically improving readability.
react-syntax-highlighter is the dominant React library for rendering syntax-highlighted code blocks. It wraps two underlying highlighting engines—highlight.js and Prism—in a declarative React component API. You pass the source code as children, specify a language, choose a theme, and the component renders fully highlighted markup. The library supports over 180 languages and ships dozens of built-in themes, including popular choices like vscDarkPlus, oneDark, and atomOneDark.
Documentation sites and developer portals use react-syntax-highlighter to render code examples inside tabbed documentation components. Users can switch between Go, TypeScript, and Python implementations of the same concept, and each tab displays properly highlighted code in the vscDarkPlus theme. The component integrates with the site’s dark-mode design system by matching the highlighter’s background to the page’s card color token.
react-syntax-highlighter ships two separate entry points: react-syntax-highlighter/dist/esm/prism and react-syntax-highlighter/dist/esm/default (highlight.js). The Prism renderer supports more languages, offers finer-grained token classification, and is generally preferred for new projects. The highlight.js renderer has a smaller base bundle and auto-detects languages. Choose one and import consistently—mixing both doubles your bundle size.
Themes in react-syntax-highlighter are plain JavaScript objects that map token types to inline styles. The library exports pre-built themes (e.g., vscDarkPlus, dracula, materialDark) from react-syntax-highlighter/dist/esm/styles/prism. You can also create custom themes by defining style rules for token classes like keyword, string, comment, function, and operator.
When using the Prism renderer, you explicitly specify the language via the language prop (e.g., "go", "typescript", "python"). The highlight.js renderer can auto-detect the language if you omit the prop, but explicit specification is more reliable and avoids misclassification. For languages not included in the default bundle, you can register additional Prism grammars at runtime.
The showLineNumbers prop adds numbered gutters to code blocks. Combined with wrapLines and lineProps, you can highlight specific lines (e.g., to call attention to changed or important lines), add click handlers to individual lines, or apply custom styles per line. This is essential for tutorial content that walks through code step by step.
The full Prism bundle includes grammars for every supported language, which can add hundreds of kilobytes to your bundle. The PrismLight (or PrismAsyncLight) variant lets you register only the languages you need, reducing the client-side payload to a fraction of the full build. For documentation sites that only show Go, TypeScript, and Python, this optimization is significant.
Under the hood, react-syntax-highlighter passes the source code string to the chosen highlighting engine (Prism or highlight.js), which tokenizes the code into an abstract syntax tree of classified spans. The library then converts this AST into React elements, applying inline styles from the selected theme object to each token. The result is a tree of <span> elements inside a <pre><code> wrapper, each styled according to its syntactic role.
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism";
interface CodeBlockProps {
code: string;
language: string;
}
export function CodeBlock({ code, language }: CodeBlockProps) {
return (
<SyntaxHighlighter
language={language}
style={vscDarkPlus}
showLineNumbers={true}
wrapLongLines={true}
>
{code}
</SyntaxHighlighter>
);
}
import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter";
import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism";
// Register only the languages you need
import go from "react-syntax-highlighter/dist/esm/languages/prism/go";
import typescript from "react-syntax-highlighter/dist/esm/languages/prism/typescript";
import python from "react-syntax-highlighter/dist/esm/languages/prism/python";
import bash from "react-syntax-highlighter/dist/esm/languages/prism/bash";
SyntaxHighlighter.registerLanguage("go", go);
SyntaxHighlighter.registerLanguage("typescript", typescript);
SyntaxHighlighter.registerLanguage("python", python);
SyntaxHighlighter.registerLanguage("bash", bash);
export function LightCodeBlock({ code, language }: { code: string; language: string }) {
return (
<SyntaxHighlighter language={language} style={vscDarkPlus}>
{code}
</SyntaxHighlighter>
);
}
A documentation site, for instance, might use a tabbed interface that lets users switch between language implementations. Each tab renders a SyntaxHighlighter instance with the appropriate language and code content.
import { useState } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism";
interface Tab {
label: string;
language: string;
code: string;
}
interface TabbedCodeProps {
tabs: Tab[];
}
export function TabbedCode({ tabs }: TabbedCodeProps) {
const [activeIndex, setActiveIndex] = useState(0);
const active = tabs[activeIndex];
return (
<div className="tabbed-code">
<div className="tab-bar">
{tabs.map((tab, i) => (
<button
key={tab.label}
className={i === activeIndex ? "tab active" : "tab"}
onClick={() => setActiveIndex(i)}
>
{tab.label}
</button>
))}
</div>
<SyntaxHighlighter
language={active.language}
style={vscDarkPlus}
showLineNumbers
customStyle={{
margin: 0,
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
background: "#1c1f2e",
}}
>
{active.code}
</SyntaxHighlighter>
</div>
);
}
In a typical documentation project, API reference pages include live code examples. Each endpoint’s page includes request and response examples highlighted with the appropriate language (JSON for payloads, bash for curl commands, Go and TypeScript for SDK usage). The vscDarkPlus theme was chosen because it matches VS Code’s default dark theme, which is familiar to the majority of developers visiting the docs.
The website project’s blog and tutorial sections embed code blocks within MDX content. A custom MDX component maps fenced code blocks (```go) to react-syntax-highlighter, so authors write standard Markdown and get highlighted output automatically. The language tag from the fence is passed directly to the language prop, and the component adds a copy-to-clipboard button overlaid on the top-right corner of each block.
Interactive playground pages use react-syntax-highlighter in a read-only display panel alongside an editable textarea. As users type code in the editor, the display panel re-renders with updated highlighting in real time. This split-pane approach avoids the complexity of contentEditable-based editors while still providing visual feedback during editing.
Rather than using a built-in theme verbatim, you can extend one to match your application’s color tokens. Clone the theme object, override the background to match your card color, and adjust specific token colors to maintain contrast against your custom background.
import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism";
// Extend vscDarkPlus with custom background matching design system
const customTheme = {
...vscDarkPlus,
'pre[class*="language-"]': {
...vscDarkPlus['pre[class*="language-"]'],
background: "var(--bg-card, #1c1f2e)",
borderRadius: "8px",
border: "1px solid var(--border, #2a2d40)",
},
'code[class*="language-"]': {
...vscDarkPlus['code[class*="language-"]'],
background: "none",
},
};
import { useState, useCallback } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism";
export function CopyableCodeBlock({ code, language }: { code: string; language: string }) {
const [copied, setCopied] = useState(false);
const handleCopy = useCallback(async () => {
await navigator.clipboard.writeText(code);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}, [code]);
return (
<div style={{ position: "relative" }}>
<button
onClick={handleCopy}
style={{
position: "absolute", top: 8, right: 8, zIndex: 1,
background: "rgba(255,255,255,0.1)", border: "none",
color: "#e8e6f0", padding: "4px 8px", borderRadius: "4px",
cursor: "pointer", fontSize: "12px",
}}
>
{copied ? "Copied!" : "Copy"}
</button>
<SyntaxHighlighter language={language} style={vscDarkPlus} showLineNumbers>
{code}
</SyntaxHighlighter>
</div>
);
}
PrismLight and register only what you need. The full bundle adds ~200KB (gzipped ~60KB) to your client JavaScript.language prop is omitted or set to an unregistered language, the code renders without highlighting. This is silent—no error is thrown—so it is easy to miss during development. Always verify that your language strings match Prism’s grammar names.< instead of <), the highlighter renders the escaped entities literally. Decode HTML entities before passing code to the component.code prop is a new string reference on every render (e.g., created inline with template literals), the highlighter re-tokenizes unnecessarily. Memoize the code string or wrap the component in React.memo to avoid redundant work.react-syntax-highlighter supports SSR, but the initial server render may produce slightly different markup than the client if the Prism version or language registration differs. Use the same entry point on both server and client to ensure consistent output.