---
title: Syntax Highlighting
---

import ConfigVariants from '@components/ConfigVariants.astro'
import PropertySignature from '@components/PropertySignature.astro'
import { Tabs, TabItem } from '@astrojs/starlight/components'

Expressive Code's default syntax highlighter is Shiki, which uses the same engine as VS Code to provide accurate syntax highlighting for over 100 languages.

:::tip[No installation required]
These features are provided by `@expressive-code/plugin-shiki`, which is installed & enabled by default in all framework integrations. You can start using it right away in your documents!
:::

## Usage in markdown / MDX

### Regular syntax highlighting

To get syntax highlighting for your code blocks, wrap them in code fences (using three or more backticks) and ensure that your opening code fences include a language identifier, e.g. `js` for JavaScript:

````md title="example.md" ins=/^([^a-z]{3}.*)/
```js
console.log('This code is syntax highlighted!')
```
````

The rendered result looks like this:
```js
console.log('This code is syntax highlighted!')
```

### Rendering ANSI escape sequences

Expressive Code supports rendering a subset of [ANSI escape sequences](https://en.wikipedia.org/wiki/ANSI_escape_code). This can be useful for displaying formatted terminal output in your documentation.

To enable this feature for a code block, set its language identifier in the opening code fence to `ansi`. You can then use the following ANSI escape sequences:

````md title="ansi-example.md" ins="ansi"
```ansi
[1;4mStandard ANSI colors:[0m
- Dimmed:     [2;30m Black [2;31m Red [2;32m Green [2;33m Yellow [2;34m Blue [2;35m Magenta [2;36m Cyan [2;37m White [0m
- Foreground: [30m Black [31m Red [32m Green [33m Yellow [34m Blue [35m Magenta [36m Cyan [37m White [0m
- Background: [40m Black [41m Red [42m Green [43m Yellow [44m Blue [45m Magenta [46m Cyan [47m White [0m
- Reversed:   [7;30m Black [7;31m Red [7;32m Green [7;33m Yellow [7;34m Blue [7;35m Magenta [7;36m Cyan [7;37m White [0m

[1;4m8-bit colors (showing colors 160-171 as an example):[0m
- Dimmed:     [2;38;5;160m 160 [2;38;5;161m 161 [2;38;5;162m 162 [2;38;5;163m 163 [2;38;5;164m 164 [2;38;5;165m 165 [2;38;5;166m 166 [2;38;5;167m 167 [2;38;5;168m 168 [2;38;5;169m 169 [2;38;5;170m 170 [2;38;5;171m 171 [0m
- Foreground: [38;5;160m 160 [38;5;161m 161 [38;5;162m 162 [38;5;163m 163 [38;5;164m 164 [38;5;165m 165 [38;5;166m 166 [38;5;167m 167 [38;5;168m 168 [38;5;169m 169 [38;5;170m 170 [38;5;171m 171 [0m
- Background: [48;5;160m 160 [48;5;161m 161 [48;5;162m 162 [48;5;163m 163 [48;5;164m 164 [48;5;165m 165 [48;5;166m 166 [48;5;167m 167 [48;5;168m 168 [48;5;169m 169 [48;5;170m 170 [48;5;171m 171 [0m
- Reversed:   [7;38;5;160m 160 [7;38;5;161m 161 [7;38;5;162m 162 [7;38;5;163m 163 [7;38;5;164m 164 [7;38;5;165m 165 [7;38;5;166m 166 [7;38;5;167m 167 [7;38;5;168m 168 [7;38;5;169m 169 [7;38;5;170m 170 [7;38;5;171m 171 [0m

[1;4m24-bit colors (full RGB):[0m
- Dimmed:     [2;38;2;34;139;34m ForestGreen - RGB(34,139,34) [2;38;2;102;51;153m RebeccaPurple - RGB(102,51,153) [0m
- Foreground: [38;2;34;139;34m ForestGreen - RGB(34,139,34) [38;2;102;51;153m RebeccaPurple - RGB(102,51,153) [0m
- Background: [48;2;34;139;34m ForestGreen - RGB(34,139,34) [48;2;102;51;153m RebeccaPurple - RGB(102,51,153) [0m
- Reversed:   [7;38;2;34;139;34m ForestGreen - RGB(34,139,34) [7;38;2;102;51;153m RebeccaPurple - RGB(102,51,153) [0m

[1;4mFont styles:[0m
- Default
- [1mBold[0m
- [2mDimmed[0m
- [3mItalic[0m
- [4mUnderline[0m
- [7mReversed[0m
- [9mStrikethrough[0m
```
````

Result:

```ansi
[1;4mStandard ANSI colors:[0m
- Dimmed:     [2;30m Black [2;31m Red [2;32m Green [2;33m Yellow [2;34m Blue [2;35m Magenta [2;36m Cyan [2;37m White [0m
- Foreground: [30m Black [31m Red [32m Green [33m Yellow [34m Blue [35m Magenta [36m Cyan [37m White [0m
- Background: [40m Black [41m Red [42m Green [43m Yellow [44m Blue [45m Magenta [46m Cyan [47m White [0m
- Reversed:   [7;30m Black [7;31m Red [7;32m Green [7;33m Yellow [7;34m Blue [7;35m Magenta [7;36m Cyan [7;37m White [0m

[1;4m8-bit colors (showing colors 160-171 as an example):[0m
- Dimmed:     [2;38;5;160m 160 [2;38;5;161m 161 [2;38;5;162m 162 [2;38;5;163m 163 [2;38;5;164m 164 [2;38;5;165m 165 [2;38;5;166m 166 [2;38;5;167m 167 [2;38;5;168m 168 [2;38;5;169m 169 [2;38;5;170m 170 [2;38;5;171m 171 [0m
- Foreground: [38;5;160m 160 [38;5;161m 161 [38;5;162m 162 [38;5;163m 163 [38;5;164m 164 [38;5;165m 165 [38;5;166m 166 [38;5;167m 167 [38;5;168m 168 [38;5;169m 169 [38;5;170m 170 [38;5;171m 171 [0m
- Background: [48;5;160m 160 [48;5;161m 161 [48;5;162m 162 [48;5;163m 163 [48;5;164m 164 [48;5;165m 165 [48;5;166m 166 [48;5;167m 167 [48;5;168m 168 [48;5;169m 169 [48;5;170m 170 [48;5;171m 171 [0m
- Reversed:   [7;38;5;160m 160 [7;38;5;161m 161 [7;38;5;162m 162 [7;38;5;163m 163 [7;38;5;164m 164 [7;38;5;165m 165 [7;38;5;166m 166 [7;38;5;167m 167 [7;38;5;168m 168 [7;38;5;169m 169 [7;38;5;170m 170 [7;38;5;171m 171 [0m

[1;4m24-bit colors (full RGB):[0m
- Dimmed:     [2;38;2;34;139;34m ForestGreen - RGB(34,139,34) [2;38;2;102;51;153m RebeccaPurple - RGB(102,51,153) [0m
- Foreground: [38;2;34;139;34m ForestGreen - RGB(34,139,34) [38;2;102;51;153m RebeccaPurple - RGB(102,51,153) [0m
- Background: [48;2;34;139;34m ForestGreen - RGB(34,139,34) [48;2;102;51;153m RebeccaPurple - RGB(102,51,153) [0m
- Reversed:   [7;38;2;34;139;34m ForestGreen - RGB(34,139,34) [7;38;2;102;51;153m RebeccaPurple - RGB(102,51,153) [0m

[1;4mFont styles:[0m
- Default
- [1mBold[0m
- [2mDimmed[0m
- [3mItalic[0m
- [4mUnderline[0m
- [7mReversed[0m
- [9mStrikethrough[0m
```

## Supported languages

Out of the box, over 100 languages are supported, including JavaScript, TypeScript, HTML, CSS, Astro, Markdown, MDX, JSON, YAML, and many more. You can find a [list of all language identifiers](https://github.com/antfu/textmate-grammars-themes/blob/main/packages/tm-grammars/README.md) on GitHub.

:::tip
You can support more languages by providing their grammars to the `langs` option. See the [configuration](#configuration) section for examples on how to do this.
:::

## Differences to Shiki's HTML output

If you're migrating an existing site to Expressive Code that uses custom CSS to modify syntax-highlighted code, please note that the HTML output and classes generated by Expressive Code do not match the default output generated by Shiki.

Instead, the syntax tokens generated by Shiki are converted to Expressive Code's own annotations (`InlineStyleAnnotation`), which provide efficient multi-theme support and can be combined with other annotations.

If you need additional classes on some HTML elements, consider [writing your own plugin](/guides/developing-plugins/) to modify the output as needed.

## Configuration

When using this plugin through a framework integration, you can configure it by passing options to the integration.

Here are configuration examples for common scenarios:

<ConfigVariants
  imports={`
    // Add this if you want to load a custom language grammar from a file:
    // import fs from 'node:fs'
  `}
  settings={`
    // You can use any of the themes bundled with Shiki by name,
    // specify a path to JSON theme file, or pass an instance
    // of the \`ExpressiveCodeTheme\` class here:
    themes: ['dracula', 'solarized-light'],
    shiki: {
      // You can pass additional plugin options here,
      // e.g. to load custom language grammars:
      langs: [
        // import('./some-exported-grammar.mjs'),
        // JSON.parse(fs.readFileSync('./some-json-grammar.json', 'utf-8'))
      ],
    },
  `}
/>

### Available plugin options

You can pass the following options to the plugin:

#### bundledLangs

<PropertySignature>
- Type: BundledShikiLanguage[]
- Default: undefined
- Availability: Astro and Starlight integrations only
</PropertySignature>

Allows defining a subset of language IDs from the full Shiki bundle that should be available for syntax highlighting.

In server-side rendering (SSR) environments, setting this option to the languages used on your site can reduce bundle size by up to 80%.

If this option is not set, all languages from the full Shiki bundle are available.

<ConfigVariants
  imports={`
    import cloudflare from '@astrojs/cloudflare // no-ins'
  `}
  settings={`
    shiki: {
      // Example: Only include languages 'astro' and 'sass'
      // in the bundle, reducing SSR bundle size by 80%
      bundledLangs: ['astro', 'sass'],
    },
  `}
  nonIntegrationSettings={`
    // Server-side rendering (SSR) must be enabled
    // for bundle size trimming to have an effect
    output: 'server', // no-ins
    adapter: cloudflare(), // no-ins
  `}
  noNextJs
/>

#### engine

<PropertySignature>
- Type: `'oniguruma'` | `'javascript'`
- Default: `'oniguruma'`
</PropertySignature>

The Shiki RegExp engine to be used for syntax highlighting. The following options are available:

- `'oniguruma'`: The default engine that supports all grammars, but requires a target environment with WebAssembly (WASM) support.
- `'javascript'`: A pure JavaScript engine that does not require WASM. The Shiki team is continuously improving this engine and aims for full compatibility with the Oniguruma engine. Use this engine if your target environment does not support WASM.

#### langs

<PropertySignature>
- Type: LanguageInput[]
- Default: []
</PropertySignature>

A list of additional languages that should be available for syntax highlighting.

You can pass any of the language input types supported by Shiki, e.g.:
- `import('./some-exported-grammar.mjs')`
- `JSON.parse(fs.readFileSync('./some-json-grammar.json', 'utf-8'))`

See the [Shiki documentation](https://shiki.style/guide/load-lang) for more information.

:::caution
When using this option, Expressive Code automatically ensures that the languages bundled with Shiki are still available, unless you have limited them using the [`bundledLangs`](#bundledlangs) option.

Therefore, please do not add Shiki's bundled languages manually to this array, as this would load many unused languages and slow down both the dev server and build times.
:::

#### langAlias

<PropertySignature>
- Type: Record\<string, string\>
- Default: {}
</PropertySignature>

Allows defining alias names for languages. The keys are the alias names, and the values are the language IDs to which they should resolve.

The values can either be bundled languages, or additional languages defined in [`langs`](#langs).

<ConfigVariants
  settings={`
    shiki: {
      // Allow using the alias 'mjs' for the 'javascript' language
      langAlias: {
        mjs: 'javascript',
      },
    },
  `}
/>

#### injectLangsIntoNestedCodeBlocks

<PropertySignature>
- Type: boolean
- Default: false
</PropertySignature>

By default, the additional languages defined in `langs` are only available in top-level code blocks contained directly in their parent Markdown or MDX document.

Setting this option to `true` also enables syntax highlighting when a fenced code block using one of your additional `langs` is nested inside an outer `markdown`, `md` or `mdx` code block. Example:

`````md
````md
This top-level Markdown code block contains a nested `my-custom-lang` code block:
```my-custom-lang
This nested code block will only be highlighted using `my-custom-lang`
if `injectLangsIntoNestedCodeBlocks` is enabled.
```
````
`````

#### transformers

<PropertySignature>
- Type: ShikiTransformer[]
- Default: []
</PropertySignature>

An optional list of Shiki transformers that should be called during syntax highlighting.

This option allows you to add transformers that modify the tokens produced by Shiki to improve syntax highlighting, e.g. by applying bracket matching or changing the color of certain tokens.

:::caution[Experimental option with very limited support]
This option currently only supports transformers that meet all of the following criteria:

- They do not contain any hooks except `preprocess` and `tokens`.
- They do not attempt to modify the text contents of a code block.

As a result, **most popular transformers will not work**, including `@shikijs/transformers` and `@shikijs/twoslash`, and attempting to add them will throw an error. This is expected behavior to inform you about the current limitations, and not a bug in any of the involved projects. Please do not report such errors to other authors, as they will be unable to help.

The current limitations exist because Expressive Code's architecture is not yet compatible with Shiki's transformers API, which has been introduced long after the release of Expressive Code. We will improve the supported transformer features in future releases to close the gap.
:::

:::tip
If you have a transformer that does useful work in its `preprocess` and `tokens` hooks without modifying any text contents, but it gets rejected by Expressive Code due to other hooks being present, you can create a custom transformer that wraps the original transformer and only exposes the supported hooks.
:::

### Using another syntax highlighter

If you want to use another syntax highlighter, you can set `shiki: false` in the [configuration](#configuration) to prevent the default highlighter from being loaded. You can then write a plugin for the new syntax highlighter and add it to the `plugins` array.

:::tip
If you publish your own plugin package to NPM, please [let us know on GitHub](https://github.com/expressive-code/expressive-code/issues). We'd love to showcase high-quality community plugins.
:::
