Via https://rehype-pretty-code.netlify.app
Ideas for syntax highlighting options
rehype-pretty-code
is a Rehype plugin powered by the shikiji
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.
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!
import { useFloating, offset } from '@floating-ui/react';
interface Props {
open: boolean;
onOpenChange(open: boolean): void;
}
export function App({ open, onOpenChange }: Props) {
const { refs, floatingStyles } = useFloating({
open,
onOpenChange,
placement: 'left',
middleware: [offset(10)],
});
return (
<Container>
<div ref={refs.setReference} />
{open && <div ref={refs.setFloating} style={floatingStyles} />}
</Container>
);
}
The theme is Moonlight II with a custom background color.
Draw attention to a particular line of code.
import { useFloating } from '@floating-ui/react';
function MyComponent() {
const { refs, floatingStyles } = useFloating();
return (
<>
<div ref={refs.setReference} />
<div ref={refs.setFloating} style={floatingStyles} />
</>
);
}
Draw attention to a particular word or series of characters.
import { useFloating } from '@floating-ui/react';
function MyComponent() {
const { refs, floatingStyles } = useFloating();
return (
<>
<div ref={refs.setReference} />
<div ref={refs.setFloating} style={floatingStyles} />
</>
);
}
The result of [1, 2, 3].join('-'){:js}
is '1-2-3'{:js}
.
For instance, if you had the following code block:
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.
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: [0;36mhttp://localhost:[0;36;1m3000[0;36m/[0m{:ansi}
Install via your terminal:
npm install rehype-pretty-code shikiji@^0.9.0
This package is ESM-only and currently supports shikiji{:.string}
^0.7.0 - ^0.9.0{:.string}
.
Note: If you need
CJS
support you should userehype-pretty-code@0.10.1{:.string}
, which uses Shiki instead of Shikiji (v0.10.1 docs here). To use the latest version in Next.js, ensure your config file isESM
:next.config.mjs
. Here's a full example: rehype-pretty-code/website/next.config.mjs
The following works both on the server and on the client.
unified@11{:.string}
is used as a dependency.
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();
The following example shows how to use this package with Next.js.
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);
Make sure you have disabled the
mdxRs{:.meta.object-literal.key}
option for Next.js 13 / app dir, as it currently does not support Rehype plugins.
interface Options {
grid?: boolean;
theme?: Theme | Record<string, Theme>;
keepBackground?: boolean;
defaultLang?: string | { block?: string; inline?: string };
tokensMap?: Record<string, string>;
transformers?: ShikijiTransformer[];
filterMetaString?(str: string): string;
getHighlighter?(options: BundledHighlighterOptions): Promise<Highlighter>;
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:
const options = {
grid: false,
};
theme{:.meta.object-literal.key}
The default theme is github-dark-dimmed{:.string}
. Shikiji has a bunch of
pre-packaged themes,
which can be specified as a plain string:
const options = {
theme: 'one-dark-pro',
};
You can use your own theme as well by passing the theme JSON:
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:
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:
const options = {
defaultLang: 'plaintext',
};
Or you can also specify default languages for inline code and code blocks separately:
const options = {
defaultLang: {
block: 'plaintext',
inline: 'plaintext',
},
};
transformers{:.meta.object-literal.key}
Transformers are a
Shikiji-native way to manipulate the hAST
tree of the code blocks and further
extend the behavior of this plugin. The
shikiji-transformers
package provides some useful transformers.
import { transformerNotationDiff } from 'shikiji-transformers';
const options = {
transformers: [transformerNotationDiff()],
};
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 with
rehype-pretty-code
. This option allows you to filter out some part of the meta string before the library starts parsing it.const options = { filterMetaString: (string) => string.replace(/filename="[^"]*"/, ''), };
Place a numeric range inside {}
.
```js {1-3,4}
```
The line <span>{:html}
receives a data-highlighted-line
attribute to style
via CSS.
Place an id after #
after the {}
. This allows you to color or style lines
differently based on their id.
```js {1,2}#a {3,4}#b
```
The line <span>{:html}
receives a data-highlighted-line-id="<id>"
attribute
to style via CSS.
You can use either /
:
```js /carrot/
```
Or "
as a delimiter:
```js "carrot"
```
Different segments of chars can also be highlighted:
```js /carrot/ /apple/
```
The chars <span>{: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 /
.
```js /carrot/3-5
```
Highlight only the third to fifth instances of carrot
and any instances of
apple
.
```js /carrot/3-5 /apple/
```
Place an id after #
after the chars. This allows you to color chars
differently based on their id.
```js /age/#v /name/#v /setAge/#s /setName/#s /50/#i /'Taylor'/#i
const [age, setAge] = useState(50);
const [name, setName] = useState('Taylor');
```
const [age, setAge] = useState(50);
const [name, setName] = useState('Taylor');
The chars <span>{:html}
receives a data-chars-id="<id>"
attribute to style
via CSS.
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.
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}`.
Add a file title to your code block, with text inside double quotes (""
):
```js title="..."
```
Add a caption underneath your code block, with text inside double quotes (""
):
```js caption="..."
```
CSS counters can be used to add line numbers.
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
:
```js showLineNumbers
```
<code>{: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}
:
```js showLineNumbers{number}
```
Pass your themes to theme{:.meta.object-literal.key}
, where the keys represent
the color mode:
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):
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 <code>{:html}
and <pre>{:html}
elements will have the data attribute
data-theme="...themes"
, listing each theme value space-separated:
<code data-theme="github-dark-dimmed github-light"></code>
To customize the HTML output, you can use visitor callback hooks to manipulate the hAST elements directly:
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');
},
};
To completely configure the highlighter, use the
getHighlighter{:.entity.name.function}
option. This is helpful if you'd like
to configure other Shikiji options, such as langs{:.meta.object-literal.key}
.
import { getHighlighter } from 'shikiji';
const options = {
getHighlighter: (options) =>
getHighlighter({
...options,
langs: [
'plaintext',
async () => JSON.parse(await readFile('my-grammar.json', 'utf-8')),
],
}),
};