7 min read
— views•Syntax Highlight Code in Markdown
This post is quite old and no longer being updated. The following information may no longer work the with the latest version of the tools, libraries, frameworks, or best-practices discussed.
Here is some fancy syntax highlighting using react-syntax-highlighter
and react-markdown
in Next.js!
const Hello = () => { return ( <div> Let's dive into syntax highlighting! </div> )}export default Hello;
tsxSo how exactly is it done? Most info about this references a now deprecated way of doing this (renderer
is no longer a thing). With a bit of digging through the docs here's how you can get code syntax highlighting in Next.js (and Typescript).
npm i react-markdown react-syntax-highlighter
If you're using TypeScript, you will also want the Types. The following command will add the types package as a development dependency:
npm i @types/react-syntax-highlighter -D
ReactMarkdown
Component
I will assume you already have some page or component where you are processing Markdown. In the case of react-markdown
you may have a component called 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;
tsxIn this basic example, the ReactMarkdown
component is being passed raw markdown with the prop markdown.content
. You can change this to work for your project.
From here, let's simply add the components
prop to ReactMarkdown
. This gives us the ability to add SyntaxHighlighter
in the next steps.
// Markdown.tsximport { FC } from 'react;import ReactMarkdown from 'react-markdown';type MarkdownProps = { markdown: string & { content?: string };};const Markdown: FC<MarkdownProps> = ({ markdown }) => { const MarkdownComponents: object = { // SyntaxHighlight code will go here } return ( <ReactMarkdown components={MarkdownComponents} > {markdown.content} </ReactMarkdown> )}export default Markdown;
tsxThe official documentation covers this quite well, but here's the tl;dr:
- Unless you want a massive JS bundle on your markdown pages, you should only be using the Light Build. You will need to manually import the specific languages you are going to be syntax highlighting in your markdown.
- If you're using Next.js, change the theme import from
…/dist/esm/…
to…/dist/cjs/…
. Hopefully ESM is fully supported in Next.js soon. - If you want to syntax highlight TSX/JSX you must use Prism. Highlight.js does not support TSX or JSX.
For the rest of this guide, I will be using the Prism Light Build with cjs imports. I will add TSX, TypeScript, SCSS, Bash, Markdown, and JSON support. Simply add whichever languages you'd like to use. Check here for languages supported by Prism.
// Markdown.tsximport { 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);const Markdown: FC<MarkdownProps> = ({ markdown }) => { type MarkdownProps = { markdown: string & { content?: string }; }; const MarkdownComponents: object = { // SyntaxHighlight code will go here } return ( <ReactMarkdown components={MarkdownComponents} > {markdown.content} </ReactMarkdown> )}export default Markdown;
tsxSyntaxHighlighter
component and add Line Highlighting Functionality
Let's finally start adding code for our ReactMarkdown
custom component. We will import a theme and parse-numeric-range
for the line highlight 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 = (applyHighlights: number) => { if (hasMeta) { const RE = /{([\d,-]+)}/; const metadata = node.data.meta?.replace(/\s/g, ''); const strlineNumbers = RE?.test(metadata) ? RE?.exec(metadata)[1] : '0'; const highlightLines = rangeParser(strlineNumbers); const highlight = highlightLines; const data: string = highlight.includes(applyHighlights) ? 'highlight' : null; return { data }; } else { 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- First we check for the existence of the metadata to prevent TypeScript errors if it's undefined.
- Inside of the
applyHighlights
function, we declareRE
as our Regex checker. - Then we declare a variable called
metadata
that removes any spaces from the object. strlineNumbers
checks the metadata against the Regex and ensures the object contains only numbers, hyphens, commas.rangeParser()
spits out an array of line numbers based upon the range we defined in markdown!- We then check which lines to highlight and apply a data attribute
data="highlight"
on the spans wrap the lines we defined.
We now have the ability to style data="highlight"
with some CSS. I will leave this up to you, as it will be highly dependent on the look and style you are going for.
So now that you have your component code in place for React-Markdown
and SyntaxHighlighter
, how do you actually highlight lines of code within your markdown?
```tsx {3-4, 8}
markdownSimply add curly braces with comma separated line numbers and/or ranges of line numbers!
#Adding Custom CSSYou can easily target your ReactMarkdown
and SyntaxHighlighter
components like so. You may have to use some !important
tags to target certain elements within the Syntax Highlighting, which is less than ideal, but it works.
The following example is CSS-in-JS (Emotion specifically). Notice the class names and refactor the following for your CSS setup. I plan to update this guide to use Tailwind CSS in the future.
// Markdown.tsx...const Markdown: FC<MarkdownProps> = ({ markdown }) => { ... const MarkdownComponents: object = { const styleMarkdown = css({ '.codeStyle, pre, code, code span': { // Your SyntaxHighlighter override styles here }, code: { // Your general code styles here }, 'pre code': { // Your code-block styles here }, 'h3 code': { color: 'inherit' }, 'span.linenumber': { display: 'none !important' }, '[data="highlight"]': { // Your custom line highlight styles here }, }) code({ node, inline, className, ...props }) { ... return hasLang ? ( <SyntaxHighlighter className="codeStyle" ... > {props.children} </SyntaxHighlighter> ) } } return ( <ReactMarkdown ... css={styleMarkdown} > {markdown.content} </ReactMarkdown> )}...
tsxThere is a known issue with Prism where your CSS background color for the line highlight will not extend past the viewport. So when you scroll horizontally on a mobile screen, the highlighting abruptly ends.
There are a lot of over-engineered recommendations to fix it, including wrapping the pre tag in a container to apply more styles. The fix I came up with is hacky, yet elegant:
code: {
transform: translateZ(0);
min-width: 100%;
float: left;
& > span {
display: block;
}
}
scssI had almost forgotten about floats but hey... it works 🤷
#Removing Line NumbersThe lineNumber prop must be set to true
or the line highlighting won't work at all. If you'd like to disable the line numbers, this should do the trick:
span.linenumber { display: none; }
scssA better solution would be to write some additional functions to accept more meta strings for doing things like setting titles, disabling/enabling line numbers, etc. but I'll leave that for another day (and a future guide).
Whew, there you have it. Syntax highlighting your markdown in React using TypeScript with custom line highlighting. 🙌
Thanks to Prince for the excellent writeup and function from which I took inspiration. His guide is fantastic if you are using Gatsby and/or prism-react-renderer,
Enjoy this post? Like and share!