Skip to content

Fix: Prism React Renderer Not Working — No Syntax Colors, Wrong Language, or Custom Theme Not Applying

FixDevs ·

Quick Answer

How to fix prism-react-renderer issues — Highlight component setup, language support, custom themes, line highlighting, copy button, and integration with MDX and documentation sites.

The Problem

The code block renders but has no colors:

import { Highlight, themes } from 'prism-react-renderer';

function CodeBlock({ code }: { code: string }) {
  return (
    <Highlight code={code} language="typescript" theme={themes.nightOwl}>
      {({ tokens, getLineProps, getTokenProps }) => (
        <pre>
          {tokens.map((line, i) => (
            <div key={i} {...getLineProps({ line })}>
              {line.map((token, j) => (
                <span key={j} {...getTokenProps({ token })} />
              ))}
            </div>
          ))}
        </pre>
      )}
    </Highlight>
  );
}
// Renders plain text without syntax highlighting

Or a language isn’t recognized:

Language "rust" is not supported

Or the v2 migration breaks existing code:

Module '"prism-react-renderer"' has no exported member 'Prism'

Why This Happens

prism-react-renderer is a React component that uses PrismJS for syntax highlighting without dangerouslySetInnerHTML:

  • Version 2 changed the API significantly — v1 used Highlight as a default export with Prism passed as a prop. v2 exports Highlight as a named export and bundles Prism internally. Mixing v1 and v2 APIs causes import errors.
  • Limited languages are bundled by default — to keep the bundle small, prism-react-renderer only includes ~30 common languages. Rust, Go, Ruby, and many others need additional Prism languages loaded separately.
  • The theme must be passed to Highlight — without a theme prop, tokens are generated but have no color styles. The render prop gives you un-styled tokens that you must apply styles to.
  • The style from getLineProps/getTokenProps contains the colors — if you override styles or don’t spread the props, colors are lost.

Fix 1: Basic Setup (v2)

npm install prism-react-renderer
'use client';

import { Highlight, themes } from 'prism-react-renderer';

interface CodeBlockProps {
  code: string;
  language: string;
}

function CodeBlock({ code, language }: CodeBlockProps) {
  return (
    <Highlight
      code={code.trim()}
      language={language}
      theme={themes.nightOwl}  // Built-in themes
    >
      {({ className, style, tokens, getLineProps, getTokenProps }) => (
        <pre
          className={className}
          style={{
            ...style,
            padding: '16px',
            borderRadius: '8px',
            overflow: 'auto',
            fontSize: '14px',
            lineHeight: '1.6',
          }}
        >
          {tokens.map((line, i) => (
            <div key={i} {...getLineProps({ line })}>
              {/* Optional: line numbers */}
              <span style={{
                display: 'inline-block',
                width: '2em',
                textAlign: 'right',
                marginRight: '1em',
                color: '#888',
                userSelect: 'none',
              }}>
                {i + 1}
              </span>
              {line.map((token, j) => (
                <span key={j} {...getTokenProps({ token })} />
              ))}
            </div>
          ))}
        </pre>
      )}
    </Highlight>
  );
}

// Available built-in themes:
// themes.dracula, themes.duotoneDark, themes.duotoneLight,
// themes.github, themes.jettwaveDark, themes.jettwaveLight,
// themes.nightOwl, themes.nightOwlLight, themes.oceanicNext,
// themes.okaidia, themes.oneDark, themes.oneLight,
// themes.palenight, themes.shadesOfPurple, themes.synthwave84,
// themes.ultramin, themes.vsDark, themes.vsLight

Fix 2: Add Unsupported Languages

import { Highlight, Prism, themes } from 'prism-react-renderer';

// prism-react-renderer bundles these languages:
// markup, jsx, tsx, swift, kotlin, objectivec, js-extras,
// reason, rust, graphql, yaml, go, cpp, markdown, python,
// css, javascript, typescript, json, bash, sql

// For additional languages, extend Prism:
// Note: In v2, Prism is exported from prism-react-renderer

// Add Ruby support
(typeof global !== 'undefined' ? global : window).Prism = Prism;
await import('prismjs/components/prism-ruby');

// Add PHP support
await import('prismjs/components/prism-php');

// Add Rust (already included) — just verify:
console.log(Prism.languages.rust);  // Should be defined

// Synchronous approach for build-time (SSR)
import 'prismjs/components/prism-ruby';
import 'prismjs/components/prism-php';
import 'prismjs/components/prism-java';
import 'prismjs/components/prism-csharp';
import 'prismjs/components/prism-dart';
import 'prismjs/components/prism-toml';
import 'prismjs/components/prism-docker';

function RubyCodeBlock({ code }: { code: string }) {
  return (
    <Highlight code={code} language="ruby" theme={themes.oneDark}>
      {({ className, style, tokens, getLineProps, getTokenProps }) => (
        <pre className={className} style={style}>
          {tokens.map((line, i) => (
            <div key={i} {...getLineProps({ line })}>
              {line.map((token, j) => (
                <span key={j} {...getTokenProps({ token })} />
              ))}
            </div>
          ))}
        </pre>
      )}
    </Highlight>
  );
}

Fix 3: Custom Theme

import { Highlight, type PrismTheme } from 'prism-react-renderer';

const myTheme: PrismTheme = {
  plain: {
    color: '#e0e0e0',
    backgroundColor: '#1a1a2e',
  },
  styles: [
    {
      types: ['comment', 'prolog', 'doctype', 'cdata'],
      style: { color: '#6a737d', fontStyle: 'italic' },
    },
    {
      types: ['string', 'attr-value'],
      style: { color: '#a5d6ff' },
    },
    {
      types: ['number', 'boolean'],
      style: { color: '#79c0ff' },
    },
    {
      types: ['keyword', 'operator'],
      style: { color: '#ff7b72' },
    },
    {
      types: ['function'],
      style: { color: '#d2a8ff' },
    },
    {
      types: ['class-name', 'tag'],
      style: { color: '#7ee787' },
    },
    {
      types: ['attr-name'],
      style: { color: '#79c0ff' },
    },
    {
      types: ['punctuation'],
      style: { color: '#8b949e' },
    },
    {
      types: ['variable', 'constant', 'symbol'],
      style: { color: '#ffa657' },
    },
    {
      types: ['builtin', 'char', 'selector'],
      style: { color: '#7ee787' },
    },
  ],
};

<Highlight code={code} language="typescript" theme={myTheme}>
  {/* ... */}
</Highlight>

Fix 4: Line Highlighting and Copy Button

'use client';

import { Highlight, themes } from 'prism-react-renderer';
import { useState } from 'react';

interface CodeBlockProps {
  code: string;
  language: string;
  filename?: string;
  highlightLines?: number[];  // Lines to highlight (1-indexed)
}

function CodeBlock({ code, language, filename, highlightLines = [] }: CodeBlockProps) {
  const [copied, setCopied] = useState(false);

  async function handleCopy() {
    await navigator.clipboard.writeText(code.trim());
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  }

  return (
    <div style={{ position: 'relative', borderRadius: '8px', overflow: 'hidden' }}>
      {/* Header with filename and copy button */}
      <div style={{
        display: 'flex', justifyContent: 'space-between', alignItems: 'center',
        padding: '8px 16px', background: '#1a1a2e', borderBottom: '1px solid #333',
        color: '#888', fontSize: '12px',
      }}>
        <span>{filename || language}</span>
        <button
          onClick={handleCopy}
          style={{ background: 'none', border: 'none', color: '#888', cursor: 'pointer' }}
        >
          {copied ? 'Copied!' : 'Copy'}
        </button>
      </div>

      <Highlight code={code.trim()} language={language} theme={themes.nightOwl}>
        {({ className, style, tokens, getLineProps, getTokenProps }) => (
          <pre className={className} style={{ ...style, margin: 0, padding: '16px', overflow: 'auto' }}>
            {tokens.map((line, i) => {
              const lineProps = getLineProps({ line });
              const isHighlighted = highlightLines.includes(i + 1);

              return (
                <div
                  key={i}
                  {...lineProps}
                  style={{
                    ...lineProps.style,
                    backgroundColor: isHighlighted ? 'rgba(255, 255, 255, 0.1)' : undefined,
                    borderLeft: isHighlighted ? '3px solid #60a5fa' : '3px solid transparent',
                    paddingLeft: '12px',
                    marginLeft: '-12px',
                    marginRight: '-12px',
                    paddingRight: '12px',
                  }}
                >
                  {line.map((token, j) => (
                    <span key={j} {...getTokenProps({ token })} />
                  ))}
                </div>
              );
            })}
          </pre>
        )}
      </Highlight>
    </div>
  );
}

// Usage
<CodeBlock
  code={`function greet(name: string) {
  console.log(\`Hello, \${name}!\`);
  return name;
}`}
  language="typescript"
  filename="greeting.ts"
  highlightLines={[2]}  // Highlight line 2
/>

Fix 5: MDX Integration

// components/MDXComponents.tsx — use as MDX code block renderer
import { Highlight, themes } from 'prism-react-renderer';

function MDXCodeBlock({ children, className }: {
  children: string;
  className?: string;
}) {
  // Extract language from className (e.g., "language-typescript")
  const language = className?.replace('language-', '') ?? 'text';

  // Inline code (no className) — render as <code>
  if (!className) {
    return (
      <code style={{
        background: '#f0f0f0',
        padding: '2px 6px',
        borderRadius: '4px',
        fontSize: '0.9em',
      }}>
        {children}
      </code>
    );
  }

  // Fenced code block — use Prism
  return (
    <Highlight code={children.trim()} language={language} theme={themes.nightOwl}>
      {({ className: hlClassName, style, tokens, getLineProps, getTokenProps }) => (
        <pre className={hlClassName} style={{ ...style, padding: '16px', borderRadius: '8px', overflow: 'auto' }}>
          {tokens.map((line, i) => (
            <div key={i} {...getLineProps({ line })}>
              {line.map((token, j) => (
                <span key={j} {...getTokenProps({ token })} />
              ))}
            </div>
          ))}
        </pre>
      )}
    </Highlight>
  );
}

// Register as MDX component
export const mdxComponents = {
  code: MDXCodeBlock,
  pre: ({ children }: any) => <>{children}</>,  // Unwrap <pre> — Highlight adds its own
};

Fix 6: Dual Theme (Light/Dark Mode)

'use client';

import { Highlight, themes } from 'prism-react-renderer';
import { useTheme } from 'next-themes';

function ThemedCodeBlock({ code, language }: { code: string; language: string }) {
  const { resolvedTheme } = useTheme();

  const theme = resolvedTheme === 'dark' ? themes.nightOwl : themes.nightOwlLight;

  return (
    <Highlight code={code.trim()} language={language} theme={theme}>
      {({ className, style, tokens, getLineProps, getTokenProps }) => (
        <pre className={className} style={{ ...style, padding: '16px', borderRadius: '8px' }}>
          {tokens.map((line, i) => (
            <div key={i} {...getLineProps({ line })}>
              {line.map((token, j) => (
                <span key={j} {...getTokenProps({ token })} />
              ))}
            </div>
          ))}
        </pre>
      )}
    </Highlight>
  );
}

Still Not Working?

Code renders as plain text (no colors) — verify you’re spreading {...getTokenProps({ token })} on each <span>. The token props include inline style with the color from the theme. If you override style without spreading the original, colors are lost.

v1 code doesn’t work after upgrading to v2 — v2 changed the API. import Highlight, { defaultProps, Prism } (v1) is now import { Highlight, themes, Prism } (v2). The {...defaultProps} spread on <Highlight> is no longer needed. Remove it and pass theme directly.

Language not recognized — prism-react-renderer includes ~30 languages. For others, install prismjs and import the language component: import 'prismjs/components/prism-ruby'. Set globalThis.Prism = Prism before importing.

Hydration mismatch in Next.js — if the theme depends on client state (dark mode), the server render may not match the client. Use suppressHydrationWarning on the <pre> element, or render a placeholder during SSR and the actual code block after hydration.

For related code highlighting issues, see Fix: Shiki Not Working and Fix: MDX Not Working.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles