react-syntax-highlighter Code Display

Frontend Engineering beginner Building with AI ~12 min read

Overview

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.

Core Concepts

Prism vs. highlight.js Renderers

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.

Theme Objects

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.

Language Detection and Registration

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.

Line Numbers and Line Highlighting

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.

Tree-Shakeable Lazy Loading

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.

How It Works

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.

Basic Usage with Prism

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>
  );
}

Optimized Bundle with PrismLight

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>
  );
}

Tabbed Code Examples Component

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>
  );
}

Practical Applications

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.

Implementation Patterns

Custom Theme Matching the Design System

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",
  },
};

Copy-to-Clipboard Integration

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>
  );
}

Common Pitfalls

Further Reading