Rehype Pretty Code

What's this?

This package is a Rehype plugin that provides beautiful code blocks for your MD/MDX docs. It has advantages over other solutions such as Prism. View on GitHub.

VS Code's highlighting

Leverage the accuracy of VS Code's syntax highlighting engine and the popularity of its themes ecosystem, powered by Shiki. No missing tokens or unexpected broken syntax highlighting.

import Document, {Html, Head, Main, NextScript} from 'next/document';
 
// 🔥 Super granular and accurate highlighting
export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx);
    return {...initialProps};
  }
 
  render() {
    return (
      <Html>
        <Head />
        <body className="bg-zinc-800 text-zinc-200">
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

The theme is Moonlight II with a custom background color.

There's no runtime or bundle size cost

Shiki does its highlighting at build-time — there's no perf hit caused by runtime syntax highlighting or kilobytes of library code sent over the wire on the client. Your docs will be lightning fast.

Line numbers and line highlighting are supported

import {useFloating} from '@floating-ui/react-dom';
 
function MyComponent() {
  const {x, y, reference, floating} = useFloating();
 
  return (
    <>
      <div ref={reference} />
      <div ref={floating} />
    </>
  );
}

Word highlighting too

import {useFloating} from '@floating-ui/react-dom';
 
function MyComponent() {
  const {x, y, reference, floating} = useFloating();
 
  return (
    <>
      <div ref={reference} />
      <div ref={floating} />
    </>
  );
}

Even inline code!

The result of [1, 2, 3].join('-') is '1-2-3'.

Context-aware inline code

For instance, if you had the following code block:

function getStringLength(str) {
  return str.length;
}

When we refer to getStringLength as a plain variable, we can color it as a function. Same with function, or str vs. str, etc. This allows you to semantically tie inline code with the nearest code block it's referring to.

Code blocks are unstyled

No need to fight CSS rules, build the look from scratch. We recommend the following styles as a base though:

pre > code {
  display: grid;
}

This allows line highlighting to span the width of a horizontally-scrollable code block on small viewport widths.

Installation

Install via your terminal:

npm install rehype-pretty-code shiki

Usage

The following example shows how to use this package with Next.js and MDX (this website's stack).

const rehypePrettyCode = require('rehype-pretty-code');
const fs = require('fs');
 
const options = {
  // Use one of Shiki's packaged themes
  theme: 'one-dark-pro',
  // Or your own JSON theme
  theme: JSON.parse(
    fs.readFileSync(require.resolve('./themes/dark.json'), 'utf-8')
  ),
  onVisitLine(node) {
    // Prevent lines from collapsing in `display: grid` mode, and
    // allow empty lines to be copy/pasted
    if (node.children.length === 0) {
      node.children = [{type: 'text', value: ' '}];
    }
  },
  // Feel free to add classNames that suit your docs
  onVisitHighlightedLine(node) {
    node.properties.className.push('highlighted');
  },
  onVisitHighlightedWord(node) {
    node.properties.className = ['word'];
  },
};
 
const withMDX = require('@next/mdx')({
  extension: /\.mdx?$/,
  options: {
    remarkPlugins: [],
    rehypePlugins: [[rehypePrettyCode, options]],
  },
});

Meta strings

Code blocks are configured via the meta string on the top codeblock fence.

Highlight lines

Place a numeric range inside {}.

```js {1-3,4}
 
```

Highlight words

Literal text, like a regex. Note regex itself is not yet supported.

```js /carrot/
 
```

Highlight only the third to fifth instances of carrot. Any numeric range can be placed after the last /.

```js /carrot/3-5
 
```

Highlight inline code

Append {:lang}‎ (e.g. {:js}‎) to the end of inline code to highlight it like a regular code block.

This is an array `[1, 2, 3]{:js}` of numbers 1 through 3.

Highlight plain text

Append {:.token}‎ to the end of the inline code to highlight it based on a token specified in your VS Code theme. Tokens start with a . to differentiate them from a language.

The name of the function is `getStringLength{:.entity.name.function}`.

You can create a map of tokens to shorten this usage throughout your docs:

const options = {
  tokensMap: {
    fn: 'entity.name.function',
  },
};
The name of the function is `getStringLength{:.fn}`.

Titles

Add a file title to your code block, with text inside double quotes (""):

```js title="..."
 
```

Line numbers

CSS counters can be used to add line numbers.

code {
  counter-reset: line;
}
 
code > .line::before {
  counter-increment: line;
  content: counter(line);
 
  /* Other styling */
  display: inline-block;
  width: 1rem;
  margin-right: 2rem;
  text-align: right;
  color: gray;
}

If you want to conditionally show them, use showLineNumbers:

```js showLineNumbers
// <code> will have a `data-line-numbers` attribute
```

Multiple themes (dark/light mode)

Because Shiki generates themes at build time, client-side theme switching support is not built in. There are two popular options for supporting something like Dark Mode with Shiki. See the Shiki docs for more info.

Pass your themes to theme, where the keys represent the color mode:

const options = {
  theme: {
    dark: JSON.parse(
      fs.readFileSync(require.resolve('./themes/dark.json'), 'utf-8')
    ),
    light: JSON.parse(
      fs.readFileSync(require.resolve('./themes/light.json'), 'utf-8')
    ),
  },
};

The <code> element will have a data attribute data-theme="[key]", e.g data-theme="light".

Now, you can use CSS to display the desired theme:

@media (prefers-color-scheme: dark) {
  code[data-theme='light'] {
    display: none;
  }
}
 
@media (prefers-color-scheme: light), (prefers-color-scheme: no-preference) {
  code[data-theme='dark'] {
    display: none;
  }
}

Custom highlighter

To completely configure the highlighter, use the getHighlighter option to provide a Shiki highlighter instance.

In order to support light and dark modes, Rehype Pretty Code provides an options object for you to extend. The theme property is preconfigured with the light or dark theme based on your theme options.

This is helpful if you'd like to configure other Shiki options, such as langs.

const {getHighlighter, BUNDLED_LANGUAGES} = require('shiki');
 
const options = {
  theme: {
    dark: JSON.parse(
      fs.readFileSync(require.resolve('./themes/dark.json'), 'utf-8')
    ),
    light: JSON.parse(
      fs.readFileSync(require.resolve('./themes/light.json'), 'utf-8')
    ),
  },
  getHighlighter: (options) =>
    getHighlighter({
      ...options,
      langs: [
        ...BUNDLED_LANGUAGES,
        {
          id: 'groq',
          scopeName: 'source.groq',
          path: './langs/vscode-sanity/grammars/groq.json',
        },
      ],
    }),
};

License

MIT • View on GitHub