[`rehype-pretty-code`](https://github.com/atomiks/rehype-pretty-code) is a Rehype plugin powered by the [`shiki`](https://github.com/shikijs/shiki) syntax highlighter that provides beautiful code blocks for Markdown or MDX. It works on both the server at build-time (avoiding runtime syntax highlighting) and on the client for dynamic highlighting.
## Editor-Grade Highlighting Enjoy the accuracy and granularity of VS Code's syntax highlighting engine and the popularity of its themes ecosystem — use any VS Code theme you want! ## Line Numbers and Line Highlighting Draw attention to a particular line of code. ```jsx {4} showLineNumbers import { useFloating } from "@floating-ui/react"; function MyComponent() { const { refs, floatingStyles } = useFloating(); return ( <>
); } ``` ## Word Highlighting Draw attention to a particular word or series of characters. ```jsx /floatingStyles/ import { useFloating } from "@floating-ui/react"; function MyComponent() { const { refs, floatingStyles } = useFloating(); return ( <>
); } ``` ## Inline Code Highlighting The result of `[1, 2, 3].join('-'){:js}` is `'1-2-3'{:js}`. ### Context Aware Inline Code For instance, if you had the following code block: ```js function getStringLength(str) { return str.length; } ``` When we refer to `getStringLength{:.entity.name.function}` as a plain variable, we can color it as a function. Same with `function{:.keyword}`, or `str{:.variable.parameter}` vs. `str{:.variable.other.object}`, etc. This allows you to semantically tie inline code with the nearest code block it's referring to. ## ANSI Highlighting ```ansi  vite v5.0.0 dev server running at: > Local: http://localhost:3000/ > Network: use `--host` to expose ready in 125ms. 8:38:02 PM [vite] hmr update /src/App.jsx ``` Inline ANSI: `> Local: http://localhost:3000/{:ansi}` --- ## Table of Contents - [Installation](#installation) - [Usage](#usage) - [Styles](#styles) - [Options](#options) - [grid](#grid) - [theme](#theme) - [keepBackground](#keepbackground) - [bypassInlineCode](#bypassInlineCode) - [defaultLang](#defaultlang) - [transformers](#transformers) - [Meta Strings](#meta-strings) - [Highlight Lines](#highlight-lines) - [Group Highlighted Lines By Id](#group-highlighted-lines-by-id) - [Highlight Chars](#highlight-chars) - [Group Highlighted Chars By Id](#group-highlighted-chars-by-id) - [Highlight Inline Code](#highlight-inline-code) - [Highlight Plain Text](#highlight-plain-text) - [Titles](#titles) - [Captions](#captions) - [Line Numbers](#line-numbers) - [Multiple Themes (Dark and Light Mode)](#multiple-themes-dark-and-light-mode) - [Visitor Hooks](#visitor-hooks) - [Custom Highlighter](#custom-highlighter) - [React Server Component](#react-server-component) --- ## Installation Install via your terminal: ```shell npm install rehype-pretty-code shiki ``` This package is ESM-only and currently supports `shiki{:.string}` `^1.0.0{:.string}`. > To use the latest version in Next.js, ensure your config file is `ESM`: > `next.config.mjs`. Here's a full example: > [rehype-pretty-code/website/next.config.mjs](https://github.com/atomiks/rehype-pretty-code/blob/master/website/next.config.mjs) ## Usage The following works both on the server and on the client. ```js /rehypePrettyCode/ import { unified } from "unified"; import remarkParse from "remark-parse"; import remarkRehype from "remark-rehype"; import rehypeStringify from "rehype-stringify"; import rehypePrettyCode from "rehype-pretty-code"; async function main() { const file = await unified() .use(remarkParse) .use(remarkRehype) .use(rehypePrettyCode, { // See Options section below. }) .use(rehypeStringify) .process("`const numbers = [1, 2, 3]{:js}`"); console.log(String(file)); } main(); ``` Note that `unified{:.string}` **v11** is used as a dependency internally. Ensure your `unified{:.string}` version is compatible. ### MDX The following example shows how to use this package with Next.js. ```js title="next.config.mjs" import fs from "node:fs"; import nextMDX from "@next/mdx"; import rehypePrettyCode from "rehype-pretty-code"; /** @type {import('rehype-pretty-code').Options} */ const options = { // See Options section below. }; const withMDX = nextMDX({ extension: /\.mdx?$/, options: { remarkPlugins: [], rehypePlugins: [[rehypePrettyCode, options]], }, }); /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true }; export default withMDX(nextConfig); ``` Disable the `mdxRs{:.meta.object-literal.key}` option if Rehype plugins do not work. ## Styles This library is unstyled. This means it does not provide a CSS file that applies styling by default. Rather, it provides logical attributes that you use to apply the styling yourself. For instance, the following CSS prevents overflow and adds padding: ```css pre { overflow-x: auto; padding: 1rem 0; } pre [data-line] { padding: 0 1rem; } ``` Features including highlighted lines, chars, and line numbers expose data attributes that enable you to add your own styling, which are explained below. ## Options ```ts interface Options { grid?: boolean; theme?: Theme | Record; keepBackground?: boolean; defaultLang?: string | { block?: string; inline?: string }; tokensMap?: Record; transformers?: ShikiTransformer[]; filterMetaString?(str: string): string; getHighlighter?(options: BundledHighlighterOptions): Promise; onVisitLine?(element: LineElement): void; onVisitHighlightedLine?(element: LineElement): void; onVisitHighlightedChars?(element: CharsElement, id: string | undefined): void; onVisitTitle?(element: Element): void; onVisitCaption?(element: Element): void; } ``` ### `grid{:.meta.object-literal.key}` A grid style is present by default which allows line highlighting to span the entire width of a horizontally-scrollable code block. You can disable this setting if necessary: ```js const options = { grid: false, }; ``` ### `theme{:.meta.object-literal.key}` The default theme is `github-dark-dimmed{:.string}`. Shiki has a bunch of [pre-packaged themes](https://shiki.style/themes#themes), which can be specified as a plain string: ```js const options = { theme: "one-dark-pro", }; ``` You can use your own theme as well by passing the theme JSON: ```js const options = { theme: JSON.parse(fs.readFileSync("./themes/moonlight-ii.json", "utf-8")), }; ``` ### `keepBackground{:.meta.object-literal.key}` To apply a custom background instead of inheriting the background from the theme: ```js const options = { keepBackground: false, }; ``` ### `defaultLang{:.meta.object-literal.key}` When no code language is specified, inline code or code blocks will not be themed (nor will the background), which may appear incongruous with others. In this case, you can specify a default language: ```js const options = { defaultLang: "plaintext", }; ``` Or you can also specify default languages for inline code and code blocks separately: ```js const options = { defaultLang: { block: "plaintext", inline: "plaintext", }, }; ``` ### `transformers{:.meta.object-literal.key}` [Transformers](https://shiki.style/guide/transformers#transformers) are a Shiki-native way to manipulate the `hAST` tree of the code blocks and further extend the behavior of this plugin. The [`@shikijs/transformers`](https://npm.im/@shikijs/transformers) package provides some useful transformers. ```js import { transformerNotationDiff } from '@shikijs/transformers'; const options = { transformers: [transformerNotationDiff()], }; ``` ## Meta Strings Code blocks are configured via the meta string on the top codeblock fence. > If your library also parses code blocks' meta strings, it may > [cause conflicts](https://github.com/atomiks/rehype-pretty-code/issues/52) > with `rehype-pretty-code`. This option allows you to filter out some part of > the meta string before the library starts parsing it. > > ```js > const options = { > filterMetaString: (string) => string.replace(/filename="[^"]*"/, ""), > }; > ``` #### Highlight Lines Place a numeric range inside `{}`. ````md ```js {1-3,4} ``` ```` **Styling**: The line `{:html}` receives a `data-highlighted-line` attribute that enables you to style via CSS. #### Group Highlighted Lines By Id Place an id after `#` after the `{}`. This allows you to color or style lines differently based on their id. ````md ```js {1,2}#a {3,4}#b ``` ```` **Styling**: The line `{:html}` receives a `data-highlighted-line-id=""` attribute that enables you to style via CSS. #### Highlight Chars You can use either `/`: ````md ```js /carrot/ ``` ```` Or `"` as a delimiter: ````md ```js "carrot" ``` ```` Different segments of chars can also be highlighted: ````md ```js /carrot/ /apple/ ``` ```` **Styling**: The chars `{:html}` receives a `data-highlighted-chars` attribute to style via CSS. To highlight only the third to fifth instances of `carrot`, a numeric range can be placed after the last `/`. ````md ```js /carrot/3-5 ``` ```` Highlight only the third to fifth instances of `carrot` and any instances of `apple`. ````md ```js /carrot/3-5 /apple/ ``` ```` #### Group Highlighted Chars By Id Place an id after `#` after the chars. This allows you to color chars differently based on their id. ````md ```js /age/#v /name/#v /setAge/#s /setName/#s /50/#i /"Taylor"/#i const [age, setAge] = useState(50); const [name, setName] = useState("Taylor"); ``` ```` ```js /age/#v /name/#v /setAge/#s /setName/#s /50/#i /"Taylor"/#i const [age, setAge] = useState(50); const [name, setName] = useState("Taylor"); ``` **Styling**: The chars `{:html}` receives a `data-chars-id=""` attribute to style via CSS. #### Highlight Inline Code Append `\{:lang}` (e.g. `\{:js}`) to the end of inline code to highlight it like a regular code block. ```md 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. ```md The name of the function is `getStringLength{:.entity.name.function}`. ``` You can create a map of tokens to shorten this usage throughout your docs: ```js const options = { tokensMap: { fn: "entity.name.function", }, }; ``` ```md The name of the function is `getStringLength{:.fn}`. ``` #### Titles Add a file title to your code block, with text inside double quotes (`""`): ````md ```js title="..." ``` ```` #### Captions Add a caption underneath your code block, with text inside double quotes (`""`): ````md ```js caption="..." ``` ```` ## Line Numbers CSS counters can be used to add line numbers. ```css {2,6-7} code { counter-reset: line; } code > [data-line]::before { counter-increment: line; content: counter(line); /* Other styling */ display: inline-block; width: 1rem; margin-right: 2rem; text-align: right; color: gray; } code[data-line-numbers-max-digits="2"] > [data-line]::before { width: 2rem; } code[data-line-numbers-max-digits="3"] > [data-line]::before { width: 3rem; } ``` If you want to conditionally show them, use `showLineNumbers`: ````md ```js showLineNumbers ``` ```` **Styling**: `{:html}` will have attributes `data-line-numbers` and `data-line-numbers-max-digits="n"`. If you want to start line numbers at a specific number, use `showLineNumbers{number}`: ````md ```js showLineNumbers{number} ``` ```` ## Multiple Themes (Dark and Light Mode) Pass your themes to `theme{:.meta.object-literal.key}`, where the keys represent the color mode: ```js const options = { theme: { dark: "github-dark-dimmed", light: "github-light", }, }; ``` Now, use the following CSS to display the variable colors — if a space is found in the theme name, then CSS variable keys based on the object are available ([more info](https://shiki.style/guide/dual-themes#light-dark-dual-themes)): ```scss code[data-theme*=" "], code[data-theme*=" "] span { color: var(--shiki-light); background-color: var(--shiki-light-bg); } @media (prefers-color-scheme: dark) { code[data-theme*=" "], code[data-theme*=" "] span { color: var(--shiki-dark); background-color: var(--shiki-dark-bg); } } ``` The `{:html}` and `
{:html}` elements will have the data attribute
`data-theme="...themes"`, listing each theme value space-separated:

```html

```

## Visitor Hooks

To customize the HTML output, you can use visitor callback hooks to manipulate
the [hAST elements](https://github.com/syntax-tree/hast#element) directly:

```js
const options = {
  onVisitLine(element) {
    console.log("Visited line");
  },
  onVisitHighlightedLine(element) {
    console.log("Visited highlighted line");
  },
  onVisitHighlightedChars(element) {
    console.log("Visited highlighted chars");
  },
  onVisitTitle(element) {
    console.log("Visited title");
  },
  onVisitCaption(element) {
    console.log("Visited caption");
  },
};
```

## Custom Highlighter

To completely configure the highlighter, use the
`getHighlighter{:.entity.name.function}` option. This is helpful if you'd like
to configure other Shiki options, such as `langs{:.meta.object-literal.key}`.

```js
import { getHighlighter } from "shiki";

const options = {
  getHighlighter: (options) =>
    getHighlighter({
      ...options,
      langs: [
        "plaintext",
        async () => JSON.parse(await readFile("my-grammar.json", "utf-8")),
      ],
    }),
};
```

## React Server Component

The [usage](#usage) works directly in React Server Components. Here's an example:

```tsx title="code.tsx"
import * as React from "react";
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
import rehypePrettyCode from "rehype-pretty-code";

export async function Code({ code }: { code: string }) {
  const highlightedCode = await highlightCode(code);
  return (
    
); } async function highlightCode(code: string) { const file = await unified() .use(remarkParse) .use(remarkRehype) .use(rehypePrettyCode, { keepBackground: false, }) .use(rehypeStringify) .process(code); return String(file); } ``` Then, import the RSC into a page or another component: ```tsx src/app/rsc/page.tsx import * as React from "react"; import { Code } from "./code.tsx"; export default async function Page() { return (
); } ``` See this example in action at [/rsc](/rsc).