---
title: Syntax Highlighting
description: All about syntax highlighting
---

# Syntax Highlighting

Nextra uses [Shiki](https://shiki.matsu.io) to do syntax highlighting at build time. It's very reliable and performant. For example, adding this in your Markdown file:

````md filename="Markdown"
```js
console.log('hello, world')
```
````

Result in this:

```js
console.log('hello, world')
```

## Features

### Inlined Code

Inlined syntax highlighting like `let x = 1{:jsx}` is also supported via the `{:}` syntax:

<Tabs items={['Example', 'Code']}>
  <Tabs.Tab>
    Inlined syntax highlighting is also supported: `let x = 1{:jsx}`
  </Tabs.Tab>

  <Tabs.Tab>
```md filename="Markdown"
Inlined syntax highlighting is also supported: `let x = 1{:jsx}`
```
  </Tabs.Tab>
</Tabs>

### Filenames and Titles

You can add a filename or a title to your code blocks by adding a `filename` attribute:

Renders:

<Tabs items={['Example', 'Code']}>
  <Tabs.Tab>
  ```js filename="example.js"
  console.log('hello, world')
  ```
  </Tabs.Tab>

  <Tabs.Tab>
````md filename="Markdown"
```js filename="example.js"
console.log('hello, world')
```
````
  </Tabs.Tab>
</Tabs>

### Highlighting Lines

You can highlight specific lines of code by adding a `{}` attribute to the code block:

<Tabs items={['Example', 'Code']}>
  <Tabs.Tab>
  ```js {1,4-5}
  import { useState } from 'react'

function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(count + 1)}>{count}</button>
}

`````
</Tabs.Tab>

<Tabs.Tab>
````md filename="Markdown"
```js {1,4-5}
import { useState } from 'react'

function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(count + 1)}>{count}</button>
}
`````

`````
  </Tabs.Tab>
</Tabs>

### Highlighting Substrings

You can highlight specific substrings of code by adding a `//` attribute to the code block:

<Tabs items={['Example', 'Code']}>
  <Tabs.Tab>
  ```js /useState/
  import { useState } from 'react'

  function Counter() {
    const [count, setCount] = useState(0)
    return <button onClick={() => setCount(count + 1)}>{count}</button>
  }
  ```
  </Tabs.Tab>

  <Tabs.Tab>
````md filename="Markdown"
```ts /useState/
import { useState } from 'react'

function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(count + 1)}>{count}</button>
}
```
`````

  </Tabs.Tab>
</Tabs>

You can highlight only a part of the occurrences of that substring by adding a number it: `/str/1`, or multiple: `/str/1-3`, `/str/1,3`.

### Line Numbers

You can add line numbers to your code blocks by adding a `showLineNumbers` attribute:

<Tabs items={['Example', 'Code']}>
  <Tabs.Tab>
```ts showLineNumbers
import { useState } from 'react'

function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(count + 1)}>{count}</button>
}

`````
  </Tabs.Tab>

  <Tabs.Tab>
````md filename="Markdown"
```ts showLineNumbers
import { useState } from 'react'

function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(count + 1)}>{count}</button>
}
`````

`````
  </Tabs.Tab>
</Tabs>

### ANSI Highlighting

You can highlight ANSI escape codes:

<Tabs items={['Example', 'Code']}>
  <Tabs.Tab>
  ```ansi filename="ANSI escape codes"
  [0m [0;32m✓[0m [0;2msrc/[0mindex[0;2m.test.ts (1)[0m
    [0;2m Test Files [0m [0;1;32m1 passed[0;98m (1)[0m
    [0;2m      Tests [0m [0;1;32m1 passed[0;98m (1)[0m
    [0;2m   Start at [0m 23:32:41
    [0;2m   Duration [0m 11ms
    [42;1;39;0m PASS [0;32m Waiting for file changes...[0m
          [0;2mpress [0;1mh[0;2m to show help, press [0;1mq[0;2m to quit
  ```
  </Tabs.Tab>

  <Tabs.Tab>
````md filename="Markdown"
```ansi filename="ANSI escape codes"
[0m [0;32m✓[0m [0;2msrc/[0mindex[0;2m.test.ts (1)[0m
  [0;2m Test Files [0m [0;1;32m1 passed[0;98m (1)[0m
  [0;2m      Tests [0m [0;1;32m1 passed[0;98m (1)[0m
  [0;2m   Start at [0m 23:32:41
  [0;2m   Duration [0m 11ms
  [42;1;39;0m PASS [0;32m Waiting for file changes...[0m
         [0;2mpress [0;1mh[0;2m to show help, press [0;1mq[0;2m to quit
```
`````

  </Tabs.Tab>
</Tabs>

## Supported Languages

Check [this list](https://github.com/shikijs/shiki/blob/main/docs/languages.md) for all supported languages.

## Customize the Theme

Nextra uses CSS variables to define the colors for tokens. You can inject a [global CSS](https://nextjs.org/docs/basic-features/built-in-css-support#adding-a-global-stylesheet) to customize them under light/dark themes. For example this is the default tokens and you can override any of these:

```css filename="styles.css"
:root {
  --shiki-color-text: #414141;
  --shiki-color-background: transparent;
  --shiki-token-constant: #1976d2;
  --shiki-token-string: #22863a;
  --shiki-token-comment: #aaa;
  --shiki-token-keyword: #d32f2f;
  --shiki-token-parameter: #ff9800;
  --shiki-token-function: #6f42c1;
  --shiki-token-string-expression: #22863a;
  --shiki-token-punctuation: #212121;
  --shiki-token-link: #22863a;
}

.dark {
  --shiki-color-text: #d1d1d1;
  --shiki-token-constant: #79b8ff;
  --shiki-token-string: #ffab70;
  --shiki-token-comment: #6b737c;
  --shiki-token-keyword: #f97583;
  --shiki-token-parameter: #ff9800;
  --shiki-token-function: #b392f0;
  --shiki-token-string-expression: #4bb74a;
  --shiki-token-punctuation: #bbb;
  --shiki-token-link: #ffab70;
}
```

## With Dynamic Content

Since syntax highlighting is done at build time, you can't use dynamic content in your code blocks. However, since MDX is very powerful there is a workaround via client JS. For example:

<Tabs items={['Example', 'Code']}>
  <Tabs.Tab>

import { useEffect, useRef } from 'react'

export function DynamicCode({ children }) {
  const ref = useRef()
  const tokenRef = useRef()
  // Find the corresponding token from the DOM
  useEffect(() => {
    if (ref.current) {
      const token = [...ref.current.querySelectorAll('code span')].find(
        (el) => el.innerText === '1',
      )
      tokenRef.current = token
    }
  }, [])
  return (
    <>
      <div ref={ref} style={{ marginTop: '1.5rem' }}>
        {children}
      </div>
      <a
        onClick={() => {
          tokenRef.current.innerText = (parseInt(tokenRef.current.innerText) || 0) + 1
        }}
        style={{
          cursor: 'pointer',
          userSelect: 'none',
        }}
      >
        Increase the number
      </a>
      <a
        onClick={() => {
          tokenRef.current.innerText = '1 + 1'
        }}
        style={{
          marginLeft: '.5rem',
          cursor: 'pointer',
          userSelect: 'none',
        }}
      >
        Change to `1 + 1`
      </a>
    </>
  )
}

<DynamicCode>
```js filename="dynamic_code.js"
function hello () {
  const x = 2 + 3
  console.log(1)
}
```
</DynamicCode>

</Tabs.Tab>

  <Tabs.Tab>
  ````jsx
  import { useEffect, useRef } from 'react'

export function DynamicCode({ children }) {
  const ref = useRef()
  const tokenRef = useRef()
  // Find the corresponding token from the DOM
  useEffect(() => {
    if (ref.current) {
      const token = [...ref.current.querySelectorAll('code span')].find(
        (el) => el.innerText === '1',
      )
      tokenRef.current = token
    }
  }, [])
  return (
    <>
      <div ref={ref} style={{ marginTop: '1.5rem' }}>
        {children}
      </div>
      <a
        onClick={() => {
          tokenRef.current.innerText = (parseInt(tokenRef.current.innerText) || 0) + 1
        }}
        style={{
          cursor: 'pointer',
          userSelect: 'none',
        }}
      >
        Increase the number
      </a>
      <a
        onClick={() => {
          tokenRef.current.innerText = '1 + 1'
        }}
        style={{
          marginLeft: '.5rem',
          cursor: 'pointer',
          userSelect: 'none',
        }}
      >
        Change to `1 + 1`
      </a>
    </>
  )
}

  <DynamicCode>
  ```js filename="dynamic_code.js"
  function hello () {
    const x = 2 + 3
    console.log(1)
  }
  ```
  </DynamicCode>
  ````
  </Tabs.Tab>
</Tabs>

This workaround has a limitation that updated content won't be re-highlighted. For example if we update the number to `1 + 1`, it will be incorrectly highlighted.

## Disable Syntax Highlighting

You can opt out of syntax highlighting for using one of your own. You can disable syntax highlighting globally by setting `codeHighlight: false` in your Nextra configuration (`next.config.js` file).

<OptionTable
  options={[
    ['codeHighlight', 'boolean', 'Enable or disable syntax highlighting.', 'codeHighlight: true'],
  ]}
/>

## Custom Grammar

Shiki accepts a [VSCode TextMate Grammar](https://code.visualstudio.com/api/language-extensions/syntax-highlight-guide) for syntax highlighting with custom language grammars.

You can provide these grammars by overriding the `getHighlighter` function in `mdxOptions.rehypePrettyCodeOptions` option in your Nextra config inside `next.config.js`:

<Tabs items={['Example', 'Code']}>
  <Tabs.Tab>

```js filename="next.config.js" {13-18}
import { BUNDLED_LANGUAGES } from 'shiki'

nextra({
  // ... other options
  mdxOptions: {
    rehypePrettyCodeOptions: {
      getHighlighter: (options) =>
        getHighlighter({
          ...options,
          langs: [
            ...BUNDLED_LANGUAGES,
            // custom grammar options, see the Shiki documentation for how to provide these options
            {
              id: 'my-lang',
              scopeName: 'source.my-lang',
              aliases: ['mylang'], // Along with id, aliases will be included in the allowed names you can use when writing markdown.
              path: '@/public/syntax/grammar.tmLanguage.json',
            },
          ],
        }),
    },
  },
})
```

  </Tabs.Tab>

<Tabs.Tab>
````mdx
```js filename="next.config.js" {13-18}
import { BUNDLED_LANGUAGES } from 'shiki'

nextra({
  // ... other options
  mdxOptions: {
    rehypePrettyCodeOptions: {
      getHighlighter: (options) =>
        getHighlighter({
          ...options,
          langs: [
            ...BUNDLED_LANGUAGES,
            // custom grammar options, see the Shiki documentation for how to provide these options
            {
              id: 'my-lang',
              scopeName: 'source.my-lang',
              aliases: ['mylang'], // Along with id, aliases will be included in the allowed names you can use when writing markdown.
              path: '@/public/syntax/grammar.tmLanguage.json',
            },
          ],
        }),
    },
  },
})
```

````

  </Tabs.Tab>
</Tabs>

## Custom Themes

Within `mdxOptions.rehypePrettyCodeOptions` you may also provide custom themes instead of [relying on CSS Variables](/docs/guide/syntax-highlighting):

<Tabs items={['Example', 'Code']}>
  <Tabs.Tab>
  ```js filename="next.config.js" {4}
  nextra({
    // ... other options
    mdxOptions: {
      rehypePrettyCodeOptions: {
        // VSCode theme or built-in Shiki theme, see Shiki documentation for more information
        theme: JSON.parse(
          readFileSync('./public/syntax/arctis_light.json', 'utf8')
        )
      }
    }
  })
  ```
  </Tabs.Tab>

  <Tabs.Tab>
````md
```js filename="next.config.js" {4}
nextra({
  // ... other options
  mdxOptions: { rehypePrettyCodeOptions: {
      // VSCode theme or built-in Shiki theme, see Shiki documentation for more information
      theme: JSON.parse(
        readFileSync('./public/syntax/arctis_light.json', 'utf8')
      )
    }
  }
})
```
`````

  </Tabs.Tab>
</Tabs>
