Skip to main content
views/

Syntax Highlight Code in Markdown

Amir Ardalan

Here is an example of syntax highlighting with react-syntax-highlighter and react-markdown in Next.js:

const Hello = () => {
return (
<div>
Let's dive into syntax highlighting!
</div>
)
}
export default Hello
tsx

So, how is it done?

Much of the information available online refers to a now-deprecated approach. The renderer API is no longer available.

After some digging through the documentation, here is how you can add code syntax highlighting to a Next.js project using TypeScript.

Install the Dependencies

npm install react-markdown react-syntax-highlighter
bash

If you are using TypeScript, you will also need the corresponding type definitions:

npm install @types/react-syntax-highlighter --save-dev
bash

Configure the ReactMarkdown Component

I will assume that you already have a page or component that processes Markdown.

When using react-markdown, you may have a component named Markdown.tsx that looks something like this:

// Markdown.tsx

import { FC } from 'react'
import ReactMarkdown from 'react-markdown'

type MarkdownProps = {
  markdown: string & { content?: string }
}

const Markdown: FC<MarkdownProps> = ({ markdown }) => {
  return (
    <ReactMarkdown>
      {markdown.content}
    </ReactMarkdown>
  )
}

export default Markdown
tsx

In this basic example, the ReactMarkdown component receives raw Markdown from markdown.content. You can adapt this value to match the structure of your own project.

Next, add the components prop to ReactMarkdown. This will allow us to provide a custom renderer for code elements in the following steps.

// Markdown.tsx
import { FC } from 'react'
import ReactMarkdown from 'react-markdown'
type MarkdownProps = {
markdown: string & { content?: string }
}
const Markdown: FC<MarkdownProps> = ({ markdown }) => {
const MarkdownComponents: object = {
// Syntax highlighting code will go here
}
return (
<ReactMarkdown components={MarkdownComponents}>
{markdown.content}
</ReactMarkdown>
)
}
export default Markdown
tsx

Choose the Correct SyntaxHighlighter Import

The official documentation covers the available builds in detail, but here are the main considerations:

  • Unless you want to add a large amount of JavaScript to your Markdown pages, use the Light Build. This requires you to import and register each language you intend to highlight.
  • When this post was written, Next.js projects required changing theme imports from dist/esm to dist/cjs. Full ESM support was still being tracked in this Next.js issue.
  • To highlight TSX or JSX, use Prism. Highlight.js does not support those languages.

For the rest of this guide, I will use the Prism Light Build with CommonJS imports. I will add support for TSX, TypeScript, SCSS, Bash, Markdown, and JSON.

Import only the languages you need. You can find the full list of languages supported by Prism here.

// Markdown.tsx
import { FC } from 'react'
import ReactMarkdown from 'react-markdown'
import { PrismLight as SyntaxHighlighter } from 'react-syntax-highlighter'
import tsx from 'react-syntax-highlighter/dist/cjs/languages/prism/tsx'
import typescript from 'react-syntax-highlighter/dist/cjs/languages/prism/typescript'
import scss from 'react-syntax-highlighter/dist/cjs/languages/prism/scss'
import bash from 'react-syntax-highlighter/dist/cjs/languages/prism/bash'
import markdown from 'react-syntax-highlighter/dist/cjs/languages/prism/markdown'
import json from 'react-syntax-highlighter/dist/cjs/languages/prism/json'
SyntaxHighlighter.registerLanguage('tsx', tsx)
SyntaxHighlighter.registerLanguage('typescript', typescript)
SyntaxHighlighter.registerLanguage('scss', scss)
SyntaxHighlighter.registerLanguage('bash', bash)
SyntaxHighlighter.registerLanguage('markdown', markdown)
SyntaxHighlighter.registerLanguage('json', json)
type MarkdownProps = {
markdown: string & { content?: string }
}
const Markdown: FC<MarkdownProps> = ({ markdown }) => {
const MarkdownComponents: object = {
// Syntax highlighting code will go here
}
return (
<ReactMarkdown components={MarkdownComponents}>
{markdown.content}
</ReactMarkdown>
)
}
export default Markdown
tsx

Configure SyntaxHighlighter and Add Line Highlighting

Now we can begin adding the custom code renderer.

We will import a Prism theme and parse-numeric-range, which will handle the line-number ranges used by our highlighting logic.

// Markdown.tsx
...
import rangeParser from 'parse-numeric-range'
import { oneDark } from 'react-syntax-highlighter/dist/cjs/styles/prism'
type MarkdownProps = {
markdown: string & { content?: string }
}
const Markdown: FC<MarkdownProps> = ({ markdown }) => {
const syntaxTheme = oneDark
const MarkdownComponents: object = {
code({ node, inline, className, ...props }) {
const hasLang = /language-(\w+)/.exec(className || '')
const hasMeta = node?.data?.meta
const applyHighlights: object = (lineNumber: number) => {
if (hasMeta) {
const RE = /{([\d,-]+)}/
const metadata = node.data.meta?.replace(/\s/g, '')
const stringLineNumbers = RE.test(metadata)
? RE.exec(metadata)?.[1] || '0'
: '0'
const highlightLines = rangeParser(stringLineNumbers)
const data: string | null = highlightLines.includes(lineNumber)
? 'highlight'
: null
return { data }
}
return {}
}
return hasLang ? (
<SyntaxHighlighter
style={syntaxTheme}
language={hasLang[1]}
PreTag="div"
className="codeStyle"
showLineNumbers={true}
wrapLines={hasMeta}
useInlineStyles={true}
lineProps={applyHighlights}
>
{props.children}
</SyntaxHighlighter>
) : (
<code className={className} {...props} />
)
},
}
...
}
export default Markdown
tsx

Here is what the line-highlighting logic is doing:

  • First, we check whether metadata exists to avoid errors when it is undefined.
  • Inside applyHighlights, we define a regular expression that looks for line numbers enclosed in curly braces.
  • We then remove spaces from the metadata string.
  • stringLineNumbers checks the metadata against the regular expression and extracts numbers, commas, and ranges.
  • rangeParser() converts the range into an array of individual line numbers.
  • Finally, we check whether the current line should be highlighted and apply a data="highlight" attribute when it matches.

We can now target data="highlight" with CSS. The exact styling will depend on the visual design of your project.

Controlling Line Highlighting in Markdown

Once the ReactMarkdown and SyntaxHighlighter components are configured, you can specify highlighted lines directly in the Markdown code fence:

```tsx {3-4, 8}
```
markdown

Add comma-separated line numbers or ranges inside curly braces. In this example, lines 3, 4, and 8 will be highlighted.

Adding Custom CSS

You can target the rendered ReactMarkdown and SyntaxHighlighter elements with custom CSS.

You may need to use !important for some styles generated by the syntax highlighter. This is not ideal, but it can be necessary when overriding inline or library-provided styles.

// Markdown.tsx
...
const Markdown: FC<MarkdownProps> = ({ markdown }) => {
...
const styleMarkdown = css({
'.codeStyle, pre, code, code span': {
// SyntaxHighlighter override styles
},
code: {
// General code styles
},
'pre code': {
// Code block styles
},
'h3 code': {
color: 'inherit'
},
'span.linenumber': {
display: 'none !important'
},
'[data="highlight"]': {
// Custom line highlight styles
},
})
const MarkdownComponents: object = {
code({ node, inline, className, ...props }) {
...
return hasLang ? (
<SyntaxHighlighter
className="codeStyle"
...
>
{props.children}
</SyntaxHighlighter>
) : (
<code className={className} {...props} />
)
}
}
return (
<ReactMarkdown
components={MarkdownComponents}
css={styleMarkdown}
>
{markdown.content}
</ReactMarkdown>
)
}
...
tsx

Fixing Mobile Highlighting Issues

Prism has a known issue where a highlighted line's background color may not extend beyond the viewport.

When a code block is wider than a mobile screen and the user scrolls horizontally, the highlighted background can end abruptly at the original viewport width.

Some suggested solutions involve wrapping the pre element in another container and applying additional styles. The following workaround is simpler:

code: {
  transform: translateZ(0);
  min-width: 100%;
  float: left;

  & > span {
    display: block;
  }
}
scss

It relies on a float, but it resolves the issue.

Removing Line Numbers

The showLineNumbers prop must be set to true for this line-highlighting implementation to work.

To hide the visible line numbers while preserving the highlighting behavior, use the following CSS:

span.linenumber {
  display: none;
}
scss

A more flexible implementation could accept additional metadata for options such as titles or enabling and disabling line numbers, but that is outside the scope of this guide.

You should now have syntax highlighting for Markdown code blocks in React, including support for highlighting selected lines.


Thanks to Prince for the helpful write-up and the function that inspired this implementation. His guide is particularly useful for projects using Gatsby or prism-react-renderer.