shiki Documentation

A beautiful yet powerful syntax highlighter

13,874 stars TypeScript TypeScript 62 files · ~43,289 tokens #markdown#shiki#syntax-highlighting#textmate-grammar
RAW Doc

Technical Documentation: shikijs/shiki

ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference

Overview (`README.md`)

Shiki 式



A beautiful syntax highlighter based on TextMate grammar, accurate and powerful.

📚 Documentation

Branch

The main branch is currently at v4.x.

Branch Description
v3 v3.23 of Shiki
v2 v2.5 of Shiki
v1 v1.29 of Shiki
v0 v0.14 of Shiki, the legacy version

License

MIT


Overview (`packages/cli/README.md`)

shiki-cli

Shiki in the command line.

Documentation

License

MIT


Overview (`packages/codegen/README.md`)

@shikijs/codegen

Codegen for fine-grained Shiki bundles.

Documentation

License

MIT


Overview (`packages/colorized-brackets/README.md`)

@shikijs/colorized-brackets

VSCode-style colorized brackets transformer for Shiki.

Documentation

License

MIT


Overview (`packages/core/README.md`)

@shikijs/core

The core functionality of Shiki, without any grammar of themes bundled.

It's the same as importing shiki/core.


Overview (`packages/engine-javascript/README.md`)

@shikijs/engine-javascript

Engine for Shiki using JavaScript's native RegExp. Uses Oniguruma-To-ES to transpile regex syntax and behavior.

Documentation

License

MIT


Overview (`packages/engine-oniguruma/README.md`)

@shikijs/engine-oniguruma

Engine for Shiki using Oniguruma RegExp engine in WebAssembly.

Documentation

License

MIT


Overview (`packages/langs-precompiled/README.md`)

@shikijs/langs-precompiled

Precompiled languages for Shiki that can runs in pure JavaScript environment.

Requires ES2024+ environment.

Experimental

Unsupported Languages

License

MIT


Overview (`packages/langs/README.md`)

@shikijs/langs

TextMate grammars for Shiki.

License

MIT


Overview (`packages/magic-move/README.md`)

@shikijs/magic-move

Smoothly animated code blocks with shiki. Useful for code-step animations in slide decks (e.g. Slidev) and tutorials.

Documentation

License

MIT


Overview (`packages/markdown-exit/README.md`)

@shikijs/markdown-exit

markdown-exit plugin for shiki

Documentation

License

MIT


Overview (`packages/markdown-it/README.md`)

@shikijs/markdown-it

markdown-it plugin for shiki

Documentation

License

MIT


Overview (`packages/monaco/README.md`)

@shikijs/monaco

Use shiki to highlight Monaco Editor.

Documentation

License

MIT


Overview (`packages/rehype/README.md`)

rehype-shiki

rehype plugin for shiki.

Documentation

License

MIT


Overview (`packages/shiki/README.md`)

Shiki 式



A beautiful syntax highlighter based on TextMate grammars, accurate and powerful.

📚 Documentation

License

MIT


Overview (`packages/stream/README.md`)

@shikijs/stream

Streaming colorization for shiki. Useful for highlighting text streams like LLM outputs.

Documentation

License

MIT


Overview (`packages/themes/README.md`)

@shikijs/themes

TextMate themes for Shiki.

License

MIT


Overview (`packages/transformers/README.md`)

@shikijs/transformers

Common transformers for shiki, inspired by shiki-processor.

Documentation

License

MIT


Overview (`packages/twoslash/README.md`)

@shikijs/twoslash

A shiki transformer for TypeScript Twoslash.

Documentations

License

MIT


Overview (`packages/types/README.md`)

@shikijs/types

Types for Shiki.

License

MIT


Overview (`packages/vitepress-twoslash/README.md`)

@shikijs/vitepress-twoslash

Enable Twoslash support in VitePress.

Documentation

License

MIT


Index (`docs/index.md`)


layout: home

hero:
name: "Shiki 式 "
text: "Syntax highlighter"
tagline: A beautiful yet powerful syntax highlighter
image:
src: /logo.svg
alt: Shiki Logo
actions:
- theme: brand
text: Get Started
link: /guide/
- theme: alt
text: Installation
link: /guide/install/

features:

  • title: Accurate & Beautiful
    icon: 🌈
    details: TextMate grammar powered, same engine as your VS Code. Improves with VS Code.
  • title: Zero Runtime
    icon: ⏱️
    details: Runs ahead of time, ship zero JavaScript while getting the perfect syntax highlighting.
  • title: Customizable
    icon: 🧩
    details: HAST based. Addons, transformations, highly customizable.
  • title: ESM & Universal
    icon: 🎄
    details: Fully tree-shakable ESM, runs on any JavaScript runtime, Browser, Node.js, Cloudflare Workers, etc.


Guide Index (`docs/guide/index.md`)


outline: deep

Introduction

Shiki (式, a Japanese word for "Style") is a beautiful and powerful syntax highlighter based on TextMate grammar and themes, the same engine as VS Code's syntax highlighting. Provides very accurate and fast syntax highlighting for almost any mainstream programming language.

No custom RegExp to maintain, no custom CSS to maintain, no custom HTML to maintain. And as your favorite languages and themes in VS Code evolve - your syntax highlighting will evolve too.

Oh by the way, all the code blocks in this site are highlighted by Shiki, as you'd expect :)

Features

Playground

Here is a little playground for you to try out how Shiki highlights your code. The other code blocks in this docs are rendered on the build time and ships statically, while this playground is rendered on the client side in the browser. Themes and languages are loaded on demand.

Install Shiki to use it in your project.

Bundle Size

You can inspect the bundle size in detail on pkg-size.dev/shiki.

As of v1.1.6, measured at 22nd, February 2024:

Bundle Size (minified) Size (gzip) Notes
shiki 6.9 MB 1.3 MB All themes and languages as async chunks
shiki/bundle/full 6.9 MB 1.3 MB Same as shiki
shiki/bundle/web 4.2 MB 748 KB All themes and common web languages as async chunks
shiki/core 106 KB 34 KB Core engine without any themes or languages, compose on your own
shiki/wasm 623 KB 231 KB WASM binary inlined as base64 string

Api (`docs/api.md`)

API References

`codeToHast`

You can also get the intermediate hast to do custom rendering without serializing them into HTML with codeToHast. You can also further integrate the AST with the unified ecosystem.

text
/* Detailed source-code truncated for AI context efficiency. */
ts
{
  type: 'root',
  children: [
    {
      type: 'element',
      tagName: 'pre',
      properties: {
        class: 'shiki vitesse-light',
        style: 'background-color:#ffffff;color:#393a34',
        tabindex: '0'
      },
      children: [
        {
          type: 'element',
          tagName: 'code',
          properties: {},
          children: [
            {
              type: 'element',
              tagName: 'span',
              properties: { class: 'line' },
              children: [
                {
                  type: 'element',
                  tagName: 'span',
                  properties: { style: 'color:#AB5959' },
                  children: [ { type: 'text', value: 'const' } ]
                },
                {
                  type: 'element',
                  tagName: 'span',
                  properties: { style: 'color:#B07D48' },
                  children: [ { type: 'text', value: ' a' } ]
                },
                {
                  type: 'element',
                  tagName: 'span',
                  properties: { style: 'color:#999999' },
                  children: [ { type: 'text', value: ' =' } ]
                },
                {
                  type: 'element',
                  tagName: 'span',
                  properties: { style: 'color:#2F798A' },
                  children: [ { type: 'text', value: ' 1' } ]
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

Languages (`docs/languages.md`)

Languages

Bundled Languages

Language grammars listed below are re-distributed via tm-grammars into the shiki package.

[!NOTE]
Shiki does NOT control/maintain the grammars. If you want to contribute to the grammars, please refer to textmate-grammars-themes.

Grammars are covered by their repositories's respective licenses, which are permissive (apache-2.0, mit, etc), and made available in this NOTICE.

For loading your custom languages, please reference to this guide.

Special Languages

Plain Text

You can set lang to text to bypass highlighting. This is useful as the fallback when you receive user specified language that are not available. For example:

txt
import { codeToHtml } from 'shiki'

const html = codeToHtml('console.log("Hello World")', {
  lang: 'text', // [!code hl]
  theme: 'vitesse-light',
})

txt, plain are provided as aliases to text as well.

ANSI

A special processed language ansi is provided to highlight terminal outputs. For example:

ansi
colored foreground
colored background

bold text
dimmed text
underlined text
reversed text
strikethrough text
underlined + strikethrough text

Check the raw markdown of code snippet above.


Package.Json (`docs/package.json`)

{
"type": "module",
"private": true,
"scripts": {
"docs:dev": "vitepress dev",
"docs:build": "vitepress build",
"docs:preview": "vitepress preview"
},
"devDependencies": {
"@braintree/sanitize-url": "catalog:docs",
"@unocss/reset": "catalog:docs",
"@vueuse/core": "catalog:docs",
"cytoscape": "catalog:docs",
"cytoscape-cose-bilkent": "catalog:docs",
"dayjs": "catalog:docs",
"debug": "catalog:docs",
"floating-vue": "catalog:docs",
"fuse.js": "catalog:docs",
"pinia": "catalog:cli",
"tm-grammars": "catalog:inlined",
"tm-themes": "catalog:inlined",
"unocss": "catalog:docs",
"unplugin-vue-components": "catalog:docs",
"vitepress": "catalog:docs",
"vitepress-plugin-group-icons": "catalog:docs",
"vue": "catalog:docs"
}
}


Themes (`docs/themes.md`)

Themes

Bundled Themes

Themes listed below are re-distributed via tm-themes into the shiki package.

[!NOTE]
Shiki does NOT control/maintain the themes. If you want to contribute to the themes, please refer to textmate-grammars-themes.

Themes are covered by their repositories' respective licenses, which are permissive (apache-2.0, mit, etc), and made available in this NOTICE.

For loading your custom themes, please reference to this guide.

Special Themes

You can set theme to none to bypass highlighting. This is useful as the fallback when you receive user specified theme names that are not available. For example:

ts twoslash theme:none
// @twoslash-cache: {"v":1,"hash":"86ec614e5e60566b55ed222e8e581ca015d4e3a6c7f2bd3716fca6224c3c0fda","data":"N4Igdg9gJgpgziAXAbVAFwJ4AcZJACwgDcYAnEAGhDRgA808AKAQwBsBLZuASgAIBjCGDhoB0GABUIACTQBbVol6NBsJSNLswAcwq8IWNOyFwlAYXFTpXNAHlDx4QB4AQgFcwUVjCgAZZjpuzNoweu6e3lAS+DByMAB8fAC88bwACqQQcuxwME4aWtrxADpg7HJYEKSiqpIy8qyUICLM1UgAnFTeOmj4SACMAAxUaK0hDIggtVYNTRxguIjDU/itzPw05IjtAL4U6NiLBMRkTTT0eILCovgNShlZOXkFOvFNLW2IACwATF0wPT63xGYxgEwIsy6WkWfxWaw2p0QADY9gccHhCCRyCM6OCWBwuHxpvUFCpxOo0JodHoDEYTOZLDIbPY6c5wl4fP5AsFQrx2ZForEEtx7plsrl8pTCiUyhUqjVGbIFO9Rp8vp0QN1tL0BstVeNLorIZroUhYfxVqR1psBgBmVHUQ4Yk7Y6i4phYTI4aoYPisALaJTFEAAK2YRC4/E0hmDKtaE3aX3+gKQSeooPB/p0c1NiFtVAt8JtiB+Dsw6MmmNOOIuk0YnoMZEwfAssBZjjgAGVCt5BXFXB4OVEYnF4gA6XpCpR9mAAJRg2hylOYrIAgmAMLwAD68TtSnS+dibNgAVTKQgHER8M70LyKcc+/X6SOT2qBAFYQaQDZNJ3EcwsqYFpa1qIqWAC6BbiHg5SVNUvDAGIsAzAovA7LwABmYq8AA5HA+DsAA1uwOGlKUVwiLwtyoUkSF1EqrCMDhFEQN4Y6sBA2iMMG0gwKwHG8AA6lUrBQMG3A4XowClLwvBZoGuFhhGcBRuwhiSTJvB/jASg4ZACySbwAD0Rm8MgACEtRUaw4GlDs3BNHEoxIKAuIAnAjh4GgCA7DsQA"}
import { codeToHtml } from 'shiki'

const html = codeToHtml('console.log("Hello World")', {
  lang: 'javascript',
  theme: 'none', // [!code hl]
})

Tsconfig.Json (`docs/tsconfig.json`)

{
"extends": "../tsconfig.json",
"compilerOptions": {
"target": "ESNext",
"module": "esnext",
"moduleResolution": "Bundler",
"strict": true
}
}


Blog V2 (`docs/blog/v2.md`)


outline: deep

Shiki v2.0.0

Shiki v2.0.0 itself is a boring release.

In case you missed, there are quite some cool new features we have landed in minor releases progressively:

Version Noteable New Features
v1.1.0 Better Twoslash support
v1.3.0 New structure: inline option
v1.6.0 Scoped color replacement, thanks to @QuentinRoy
v1.8.0 Expose .dispose() method for explicit resource cleanup
v1.10.0 Introduced Grammar State for partial code highlighting
v1.15.0 Introduced JavaScript Engine with better portability and bundle size
v1.16.0 Support Synchronous Usage
v1.19.0 Introduced enableDeprecationWarnings() function for easier migration. Support object-style htmlStyle and new htmlAttrs on themed tokens.
v1.23.0 New @shikijs/colorized-brackets package, thanks to @MichaelMakesGames
v1.24.0 Improved performance and accuracy for the JavaScript engine, thanks to @slevithan
v1.25.0 Separated themes and languages into @shikijs/themes and @shikijs/languages packages
v1.26.0 Introduced pre-compiled languages package for smaller bundle and better performance
v1.27.0 New shiki-codegen package for easier fine-grained bundle creation
v1.29.0 Improved the transformer matching algorithm, introduce matchAlgorithm option. Thanks to @fuma-nama

Among all these new features, we also includes a lot of new languages support and new themes. Check out languages and themes list for the full list.

Meanwhile, huge thanks to @slevithan's great work on oniguruma-to-es that make the JavaScript engine supports 97.2% of all the languages.

Breaking Changes

There are NO hard breaking changes in v2.0.0. It serves as a stepping stone for the upcoming v3.0.0.

The only change in v2 is that Shiki will now emit warnings when you use deprecated APIs that are planned to be removed in v3. Since this might affect end users, we have made a major version bump so you can opt-in to the warnings and prepare for the future removal.

  • v1.x: Deprecated APIs are still supported, marked on type level only. With optional runtime warnings to opt-in.
  • 👉 v2.0: No breaking changes, but enable runtime deprecated warnings by default.
  • v3.0: Remove deprecated APIs, breaking changes.

Expect v3.0.0 to be released soon after v2.0.0.

Automated Migration

To help with the migration process, community member Covolute provides an automated codemod that handles most of the API changes from v1 to v2. You can run it directly with:

bash
npx covolute@latest shiki/v1-to-v2

Deprecations

We highly recommend you to migrate the depreactions as soon as possible, with the warning messages as a guide.

`getHighlighter` -> `createHighlighter`

There is no functional changes, but more like correcting the naming to avoid confusion. It should be a straightforward find-and-replace.

Since the introduction of the engine system in v1.16, WebAssembly-related dependencies are no longer a hard requirement. To facilitate tree-shaking and decouple the engines from the core, two packages have been extracted: @shikijs/engine-oniguruma and @shikijs/engine-javascript. These are also re-exported from the main package as shiki/engine/oniguruma and shiki/engine/javascript, respectively.

You might need to change your import path:

ts
import { loadWasm } from 'shiki' // [!code --]
import { loadWasm } from 'shiki/engine/oniguruma' // [!code ++]

loadWasm field in createHighlighterCore is replaced with engine field:

ts
import { createHighlighter } from 'shiki'
import { createOnigurumaEngine } from 'shiki/engine/oniguruma' // [!code ++]

const shiki = await createHighlighter({
  // ...
  loadWasm: () => import('shiki/wasm'), // [!code --]
  engine: createOnigurumaEngine(() => import('shiki/wasm')), // [!code ++]
})

Shiki Compat

The @shikijs/compat package that built for compatibility with v0.14 is now deprecated. Please migrate to the main package. This package will be removed in v3.0.

Transformers Matching Algorithm

The matchAlgorithm option for transformers was introduced in v1.29.0, allowing users to choose the matching algorithm. The default value will change from v1 to v3 in v3.0.0. We recommend explicitly setting the matchAlgorithm option to avoid future breaking changes.

Learn more.

Other Deprecations

  • createdBundledHighlighter requires a single object-style argument
  • @shikijs/core
    • The regex engines createJavaScriptRegexEngine createOnigurumaEngine are no longer included, import them from @shikijs/engine-oniguruma and @shikijs/engine-javascript respectively
    • createHighlighterCore now explicits requires an engine field to be passed
    • loadWasm field in createHighlighterCore is replaced with engine field
    • @shikijs/core/wasm-inline is replaced with @shikijs/engine-oniguruma/wasm-inline
    • Import FontStyle and StackElementMetadata from @shikijs/vscode-textmate instead of @shikijs/core

Tweaking Warnings

If you prefer hard errors instead of warnings, you can run the following code before using Shiki, the first argument decides if warnings should be enabled, the second argument decides if warnings should be thrown as errors:

ts
import { enableDeprecationWarnings } from 'shiki/core'

enableDeprecationWarnings(true, true) // enable warnings and throw errors

// use crateHighlighter(...) etc. after that

Disable Warnings

If you want to disable the warnings:

ts
import { enableDeprecationWarnings } from 'shiki/core'

enableDeprecationWarnings(false)

Try It as a User

If you are using Shiki indirectly via other packages, like vitepress or @nuxt/content, where you don't directly control the version of Shiki, you can try adding the following lines to your package.json to force the usage of Shiki v2.0.0. This will help you check if the frameworks/tools you are using rely on deprecated Shiki APIs. If they do, please report it to their repositories to raise awareness of the upcoming changes. Thanks!

json
{
  "resolutions": {
    "shiki": "^2",
    "@shikijs/core": "^2",
    "@shikijs/transformers": "^2",
    "@shikijs/markdown-it": "^2",
    "@shikijs/rehype": "^2"
  }
}

Feedback

Any feedbacks are welcome! Feel free to open an issue on GitHub and let us know your thoughts.


Blog V3 (`docs/blog/v3.md`)


outline: deep

Shiki v3.0.0

Shiki v3.0.0 is another boring release that removes deprecated APIs that have been marked since v1.x and explicitly warned in v2.x.

In Shiki, new features are shipped in minor releases progressively, while the major releases is for cleaning up and removing deprecated APIs.

If you are still on v1.x, please migrate to v2.x first. It should provides you a smooth transition path to v3.0.0.

Breaking Changes

  • v1.x: Deprecated APIs are still supported, marked on type level only. With optional runtime warnings to opt-in.
  • v2.0: No breaking changes, but enable runtime deprecated warnings by default.
  • 👉 v3.0: Remove deprecated APIs, breaking changes.

`getHighlighter` -> `createHighlighter`

There is no functional changes, but more like correcting the naming to avoid confusion. It should be a straightforward find-and-replace.

Since the introduction of the engine system in v1.16, WebAssembly-related dependencies are no longer a hard requirement. To facilitate tree-shaking and decouple the engines from the core, two packages have been extracted: @shikijs/engine-oniguruma and @shikijs/engine-javascript. These are also re-exported from the main package as shiki/engine/oniguruma and shiki/engine/javascript, respectively.

You might need to change your import path:

ts
import { loadWasm } from 'shiki' // [!code --]
import { loadWasm } from 'shiki/engine/oniguruma' // [!code ++]

loadWasm field in createHighlighterCore is replaced with engine field:

ts
import { createHighlighter } from 'shiki'
import { createOnigurumaEngine } from 'shiki/engine/oniguruma' // [!code ++]

const shiki = await createHighlighter({
  // ...
  loadWasm: () => import('shiki/wasm'), // [!code --]
  engine: createOnigurumaEngine(() => import('shiki/wasm')), // [!code ++]
})

Meanwhile, @shikijs/core package no longer includes the engines or wasm binaries. You need to import them from the @shikijs/engine-oniguruma and @shikijs/engine-javascript packages.

Shiki Compat

The @shikijs/compat package that built for compatibility with v0.14 is now discontinued. Please migrate to the main package.

Transformers Matching Algorithm

The matchAlgorithm option for transformers was introduced in v1.29.0, allowing users to choose the matching algorithm. The default value is change from v1 to v3 in v3.0.0.

Learn more.

Other Breaking Changes

  • createdBundledHighlighter requires a single object-style argument
  • @shikijs/core
    • The regex engines createJavaScriptRegexEngine createOnigurumaEngine are no longer included, import them from @shikijs/engine-oniguruma and @shikijs/engine-javascript respectively
    • @shikijs/core/wasm-inline is replaced with @shikijs/engine-oniguruma/wasm-inline
    • Import FontStyle and StackElementMetadata from @shikijs/vscode-textmate instead of @shikijs/core

Try It as a User

If you are using Shiki indirectly via other packages, like vitepress or @nuxt/content, where you don't directly control the version of Shiki, you can try adding the following lines to your package.json to force the usage of Shiki v2.0.0 first.

json
{
  "resolutions": {
    "shiki": "^2",
    "@shikijs/core": "^2",
    "@shikijs/types": "^2",
    "@shikijs/transformers": "^2",
    "@shikijs/markdown-it": "^2",
    "@shikijs/rehype": "^2"
  }
}

Then run the tool to see if it emits any warnings. If they do, please report it to their repositories to raise awareness of the upcoming changes. And if not, the tool should work as expected with Shiki v3.0.0, where you can then further migrate to v3 as:

json
{
  "resolutions": {
    "shiki": "^3",
    "@shikijs/core": "^3",
    "@shikijs/types": "^3",
    "@shikijs/transformers": "^3",
    "@shikijs/markdown-it": "^3",
    "@shikijs/rehype": "^3"
  }
}

Feedback

Any feedbacks are welcome! Feel free to open an issue on GitHub and let us know your thoughts.


Blog V4 (`docs/blog/v4.md`)


outline: deep

Shiki v4.0.0

Shiki v4.0.0 drops support for Node.js 18 and remove deprecated APIs.

In Shiki, new features are shipped in minor releases progressively, while the major releases are for cleaning up and removing deprecated APIs.

Breaking Changes

Node.js 18 Dropped

Shiki v4 requires Node.js ≥ 20. Node.js 18 reached End-of-Life in April 2025 and is no longer supported.

If you are running Node.js 18, please upgrade to Node.js 20 LTS or later before upgrading to Shiki v4.

Deprecated APIs Removed

`CreatedBundledHighlighterOptions` Removed

The CreatedBundledHighlighterOptions interface (a typo, with "Created" instead of "Create") has been removed. Use CreateBundledHighlighterOptions instead:

ts
import type { CreatedBundledHighlighterOptions } from 'shiki' // [!code --]
import type { CreateBundledHighlighterOptions } from 'shiki' // [!code ++]

`createdBundledHighlighter` Removed

The createdBundledHighlighter function (a typo, with "created" instead of "create") has been removed. Use createBundledHighlighter instead:

ts
import { createdBundledHighlighter } from 'shiki' // [!code --]
import { createBundledHighlighter } from 'shiki' // [!code ++]

`theme` Option Removed in `TwoslashFloatingVue`

The singular theme option passed to TwoslashFloatingVue (from @shikijs/vitepress-twoslash) has been removed. Use themes instead:

ts
app.use(TwoslashFloatingVue, {
  theme: { /* ... */ }, // [!code --]
  themes: { /* ... */ }, // [!code ++]
})

CSS Class `twoslash-query-presisted` Removed

The misspelled CSS class twoslash-query-presisted (a typo, with "presisted" instead of "persisted") has been removed. Use the correct twoslash-query-persisted instead.


Guide Best Performance (`docs/guide/best-performance.md`)


outline: deep

Best Performance Practices

This guide will help you to improve the performance of your Shiki usage.

Cache the Highlighter Instance

The highlighter instance is expensive to create. Most of the time, you should create the highlighter instance once and reuse it for multiple highlight operations (singleton pattern).

For example:

ts
import { createHighlighterCore } from 'shiki/core'

const highlighterPromise = createHighlighterCore({ /* ... */ })

export async function highlightCode(code: string, lang: string) {
  const highlighter = await highlighterPromise
  return highlighter.codeToHtml(code, lang)
}

When you no longer need a highlighter instance, you can call the dispose() method to release the resources. (It can't be GC-ed automatically, you need to do it explicitly.)

ts
highlighter.dispose()

Fine-Grained Bundle

The pre-built bundles are for easy usage, and mostly intended for a Node.js environment where you aren't worried about the bundle size. If you are building a web application or in a resource-constrained environment, it's always better to use the fine-grained bundles to reduce the bundle size and memory usage.

Avoid importing shiki, shiki/bundle/full, shiki/bundle/web directly.

Instead, import fine-grained modules like shiki/core, shiki/engine/javascript, @shikijs/langs/typescript, @shikijs/themes/dark-plus, etc.

ts
import { createHighlighterCore } from 'shiki/core'
import { createJavaScriptRegexEngine } from 'shiki/engine/javascript'

const highlighter = await createHighlighterCore({
  themes: [
    import('@shikijs/themes/nord'),
    import('@shikijs/themes/dark-plus'),
    // ...
  ],
  langs: [
    import('@shikijs/langs/typescript'),
    import('@shikijs/langs/javascript'),
    // ...
  ],
  engine: createJavaScriptRegexEngine()
})

To compose the fine-grained bundles easily, we also provide the shiki-codegen tool to generate fine-grained bundles for you.

Learn more about Fine-Grained Bundles.

Use Shorthands

createHighlighter and createHighlighterCore load all the themes and languages upfront to ensure subsequent highlight operations are synchronous. This can add significant overhead to startup time, especially when you have a lot of themes and languages. Shorthands abstract the theme and language loading process and maintain an internal highlighter instance underneath, only loading the necessary themes and languages when needed. When your highlighting process can be asynchronous, you can use shorthands to reduce startup time.

ts
import { codeToHtml } from 'shiki'

// Only `javascript` and `nord` will be loaded when calling `codeToHtml`
const html = await codeToHtml('const a = 1', {
  lang: 'javascript',
  theme: 'nord'
})

You can also create your own shorthands with fine-grained bundles. Check out the Create Shorthands with Fine-Grained Bundles section for more details.

JavaScript Engine and Pre-compiled Languages

Shiki provides two engines for executing regular expressions: JavaScript and Oniguruma. The Oniguruma engine is WebAssembly-based and compiled from C code, and JavaScript is a pure JavaScript engine that translates Oniguruma-flavored regexes to JavaScript regexes.

If you are bundling Shiki for the web, using the JavaScript engine results in a smaller bundle size and faster startup time. The precompiled languages can further reduce bundle size and startup time, if your target browsers support the latest RegExp features.

Check the RegExp Engines guide for more details.

Use Workers

Shiki highlights code using regular expressions, which can be CPU-intensive. You can offload the highlighting work to a Web Worker/Node Worker to avoid blocking the main thread.

::: info

🚧 We are still working on a guide for creating workers easily.

:::


Guide Bundles (`docs/guide/bundles.md`)


outline: deep

Bundles

The main shiki entries bundles all supported themes and languages via lazy dynamic imports. The efficiency shouldn't be a concern to most of the scenarios as the grammar would only be imported/downloaded when it is used. However, when you bundle Shiki into browsers runtime or web workers, even those files are not imported, they still add up to your dist size. We provide the fine-grained bundle to help you compose languages and themes one-by-one as you need.

::: info
If you are building a web application, or in a performance-sensitive environment, it's always better to use the fine-grained bundles to reduce the bundle size and memory usage. Learn more about Best Performance Practices.
:::

Bundle Presets

To make it easier, we also provide some pre-composed bundles for you to use:

`shiki/bundle/full`

Bundle Size: 6.4 MB (minified), 1.2 MB (gzip), async chunks included

The full bundle includes all themes and languages, same as the main shiki entry.

`shiki/bundle/web`

Bundle Size: 3.8 MB (minified), 695 KB (gzip), async chunks included

The bundle includes all themes and common web languages like (HTML, CSS, JS, TS, JSON, Markdown, etc.) and some web frameworks (Vue, JSX, Svelte, etc.).

Use as normal, all functions from shiki are also available in the bundle:

text
/* Detailed source-code truncated for AI context efficiency. */

Fine-grained Bundle

When importing shiki, all the themes and languages are bundled as async chunks. Normally it won't be a concern to you as they are not being loaded if you don't use them. In some cases, if you want to control what to bundle, you can use the core and compose your own bundle.

text
/* Detailed source-code truncated for AI context efficiency. */

::: info
Shorthands are only available in bundle presets. For a fine-grained bundle, you can create your own shorthands using createSingletonShorthands or port it yourself.
:::


Guide Compat (`docs/guide/compat.md`)


outline: deep

Compatibility Build

::: warning
This is a compatibility build for v0.x users. And this package is no longer maintained since v3. Please migrate to the latest version.
:::

To make deep migration easier, we provide a compatibility build that shimmed the breaking changes from v0.x. You can use it as a drop-in replacement for shiki and migrate step by step.

Installations

Set the alias to shiki in your package.json:

json
{
  "dependencies": {
    "shiki": "0.14.3", // [!code --]
    "shiki": "npm:@shikijs/[email protected]" // [!code ++]
  }
}

Check the breaking changes list to see if you need to migrate anything manually.


Guide Decorations (`docs/guide/decorations.md`)

Decorations

We provide a decorations API allowing you to wrap custom classes and attributes around ranges of your code.

text
/* Detailed source-code truncated for AI context efficiency. */

The result will be (styled with CSS in this example):

ts
// @decorations:[{"start":{"line":1,"character":0},"end":{"line":1,"character":11},"properties":{"class":"highlighted-word"}}]
const x = 10
console.log(x)

The positions can also be 0-indexed offsets relative to the code:

text
/* Detailed source-code truncated for AI context efficiency. */

It renders:

ts
// @decorations:[{"start":21,"end":24,"properties":{"class":"highlighted-word"}}]
const x = 10
console.log(x)

Negative character positions denote characters from the end of a line, starting with the line end:

ts twoslash
// @twoslash-cache: {"v":1,"hash":"f445be6013dcc1515a5189fe0a44669ee748465f55a6563c00cc29cd14e82b74","data":"N4Igdg9gJgpgziAXAbVAFwJ4AcZJACwgDcYAnEAGhDRgA808BjCMONAAgEsaBbRdgCIxmpAIZpOLAJK9KINqNINEANioAbGGADmafEgAsVNIu0xlIbjB5z1nMLkQAGKo3yLRjGuVUBfCujYjgTEZHI09HgAFKJ2onAAlFxg3gBmnjCCwhBiEtK8ADpgnDxYORxCIuKSYDLWcgpKSACMAEwaWrr6iM1G1KbmeJU51fn1GvaOLiBuHl5hPa3+gTh4hCTkxnQWUVikEDhKGEnDuTV1PAB0jWj8APKpqXDmd6QAChBw3DVyUBCMCEQIAAyiYlOwII9nhwcuwyl88mAIal2HpMrAqojLg0wcoAMx4jo6PRIACsxgGFhutkmLVc7jE8x8SwC1CCa1Cm2o22iewOZEwSQ+CJqlzsDn4YAArjwAEZhKjUxAGdogTTE7p9MFmCzi3ATBx0mYMzzeFrNZZs1ZA9YK7mRIG7faHQXsYXfFiXWaM7ySmXyrlK0nNIldJAATgppB1TBNTJphp69LmZsQrSclsw1pCG3CPMdfJdxyymPOvEuWig9yhL3enw9YF+/0BIAAomAoMinuYIaQ4fXEcjUfh0dkzp6cYplCpI2rOiTEIT+tHBkDKwnHKrvaaFiyVsFbVyIjtCwLi+6sXq/XK7UqAOxqOcawxRmNAvUbpBbuOp8OZ9k2pyeYOiATr8kcQoDqK25MteAaTk0iAABzkk+YaILO2qrsaKZ2h+abJj6CzNEh/7ZoewEns6Z4nGOoy1OWp5KJw8AAPz8G81HMfA7AAD7sFKHYwKkkxQE2AJ4Jx4ESDxkLDpkMCaDwWgcGgEDsIwpAwOIMDYoquItE4fTquhzTTFhFhMTJCAGo4S4wame5WgeQFbCBYFFkkjDqPEcD8GwpD2NoCHKM0JGhguqEWUwPlwDZaq0ouhE7j4zQqL4AC6rjQMEzCsBwVh8CWIyIhc7AALzsMARTsOwNz8MA7BXuwLgaT+ZD8E47D+DV7CVg1TWTJ1FBtbhpD8AAtM03UUL1VksX5VUabFi0AOT4Jw2j4HYW00FAE0AO45FAq3dUUvhyMpJhIKA2xaF8LB4GgCC+L4QA="}
import { DecorationItem } from 'shiki'
// ---cut---
const item: DecorationItem = {
  start: { line: 0, character: 0 },
  end: { line: 0, character: -1 },
  properties: { class: 'highlighted-word' }
}

This highlights the entire first line:

ts
// @decorations:[{"start":{"line":0,"character":0},"end":{"line":0,"character":-1},"properties":{"class":"highlighted-word"}}]
const x = 10
console.log(x)

Use Decorations in Transformers

For advanced use cases, you can use the Transformers API to have full access to the tokens and the HAST tree.

Meanwhile, if you want to append decorations within a transformer, you can do that with:

text
/* Detailed source-code truncated for AI context efficiency. */

Note that you can only provide decorations in or before the preprocess hook. In later hooks, changes to the decorations array will be ignored.


Guide Dual Themes (`docs/guide/dual-themes.md`)


outline: deep

Light/Dark Dual Themes

Shiki supports outputting light/dark dual or multiple themes. Shiki's dual themes approach uses CSS variables to store the colors on each token.

Change the theme option in codeToHtml to themes with light and dark keys to generate two themes.

text
/* Detailed source-code truncated for AI context efficiency. */

The following HTML will be generated (demo preview):

html
<pre
  class="shiki shiki-themes min-light nord"
  style="background-color:#ffffff;--shiki-dark-bg:#2e3440ff;color:#24292eff;--shiki-dark:#d8dee9ff"
  tabindex="0"
>
  <code>
    <span class="line">
      <span style="color:#1976D2;--shiki-dark:#D8DEE9">console</span>
      <span style="color:#6F42C1;--shiki-dark:#ECEFF4">.</span>
      <span style="color:#6F42C1;--shiki-dark:#88C0D0">log</span>
      <span style="color:#24292EFF;--shiki-dark:#D8DEE9FF">(</span>
      <span style="color:#22863A;--shiki-dark:#ECEFF4">"</span>
      <span style="color:#22863A;--shiki-dark:#A3BE8C">hello</span>
      <span style="color:#22863A;--shiki-dark:#ECEFF4">"</span>
      <span style="color:#24292EFF;--shiki-dark:#D8DEE9FF">)</span>
      </span>
    </code>
</pre>

To make it reactive to your site's theme, you need to add a short CSS snippet:

Query-based Dark Mode

css
@media (prefers-color-scheme: dark) {
  .shiki,
  .shiki span {
    color: var(--shiki-dark) !important;
    background-color: var(--shiki-dark-bg) !important;
    /* Optional, if you also want font styles */
    font-style: var(--shiki-dark-font-style) !important;
    font-weight: var(--shiki-dark-font-weight) !important;
    text-decoration: var(--shiki-dark-text-decoration) !important;
  }
}

Class-based Dark Mode

css
html.dark .shiki,
html.dark .shiki span {
  color: var(--shiki-dark) !important;
  background-color: var(--shiki-dark-bg) !important;
  /* Optional, if you also want font styles */
  font-style: var(--shiki-dark-font-style) !important;
  font-weight: var(--shiki-dark-font-weight) !important;
  text-decoration: var(--shiki-dark-text-decoration) !important;
}

Multiple Themes

It's also possible to support more than two themes. In the themes object, you can have an arbitrary number of themes, and specify the default theme with defaultColor option.

text
/* Detailed source-code truncated for AI context efficiency. */

span tokens would be generated with respective theme's css variables:

html
<span style="color:#1976D2;--shiki-dark:#D8DEE9;--shiki-dim:#566575">console</span>

After that, you need to apply theme's css variables on element with shiki class and tokens under it, for example, based on parent's data-theme property:

css
[data-theme='dark'] .shiki,
[data-theme='dark'] .shiki span {
  background-color: var(--s-dark-bg) !important;
  color: var(--s-dark) !important;
}

[data-theme='dim'] .shiki,
[data-theme='dim'] .shiki span {
  background-color: var(--s-dim-bg) !important;
  color: var(--s-dim) !important;
}

Demo preview

Without Default Color

If you want to take full control of the colors or avoid using !important to override, you can optionally disable the default color by setting defaultColor to false.

text
/* Detailed source-code truncated for AI context efficiency. */

With it, a token would be generated like:

html
<span style="--shiki-dark:#D8DEE9;--shiki-light:#2E3440">console</span>

In that case, the generated HTML would have no style out of the box, you need to add your own CSS to control the colors.

It's also possible to control the theme in CSS variables. For more, refer to the great research and examples by @mayank99 in this issue #6.

`light-dark()` Function

You can also use light-dark() function to avoid manually maintaining the CSS variables.

Set defaultColor to a special value light-dark() to use it. When using this, it requires both light and dark themes to be provided.

text
/* Detailed source-code truncated for AI context efficiency. */

:::info Compatibility Note
The light-dark() function is relatively new and may not be supported in older browsers. Can I use?
:::


Guide Grammar State (`docs/guide/grammar-state.md`)

Grammar State

GrammarState is a special token that holds the grammar context and allows you to highlight from an intermediate grammar state, making it easier to highlight code snippets.

For example, if we want to highlight the type annotation Pick<MyObject, string>[], we can get the grammar state with the getLastGrammarState method, and pass into the grammarState option:

ts
import { createHighlighter } from 'shiki'

const shiki = await createHighlighter({ langs: ['ts'], themes: ['github-dark'] })

const stateTypeAnnotation = shiki.getLastGrammarState('let a:', { lang: 'ts', theme: 'github-dark' })

const highlightedType = shiki.codeToHtml(
  'Pick<MyObject, string>[]',
  {
    lang: 'ts',
    theme: 'github-dark',
    grammarState: stateTypeAnnotation // <--- this
  }
)

Now Shiki would highlight correctly as it knows to start as the type annotation. You can keep that grammar state object for multiple uses as well.

Grammar Context Code

For one-off grammar context shifting, we also provide a shorthand by the grammarContextCode option:

ts
const highlightedType = shiki.codeToHtml(
  'Pick<MyObject, string>[]',
  {
    lang: 'ts',
    theme: 'github-dark',
    grammarContextCode: 'let a:' // same as above, a temporary grammar state is created internally
  }
)

Get Grammar State from HAST

The getLastGrammarState method runs the highlighting process internally and returns the grammar state. It might result in executing highlighting twice if you are working on something like pausable highlighting. In that case, you can pass the highlighted hast node to the getLastGrammarState to get the grammar state we stored in an internal WeakMap:

ts
const shiki = await getHighlighter(/* ... */)

const root = shiki.codeToHast(/* ... */)

const grammarState = shiki.getLastGrammarState(root) // pass the hast root instead of code

Guide Install (`docs/guide/install.md`)


outline: deep

Installation & Usage

Installation

Install via npm, or see CDN Usage:
::: code-group

sh [npm]
npm install -D shiki
sh [yarn]
yarn add -D shiki
sh [pnpm]
pnpm add -D shiki
sh [bun]
bun add -D shiki
sh [deno]
deno add npm:shiki

:::

Integrations

We also provide some integrations:

Usage

Shorthands

The quickest way to get started with shiki is to use the provided shorthand functions. These will load the necessary themes and languages on demand, and automatically cache them in memory.

Passing your code snippet to the codeToHtml function with the lang and theme specified, it will return a highlighted HTML string that you can embed in your page. The generated HTML contains inline style for each token, so you don't need extra CSS to style it.

text
/* Detailed source-code truncated for AI context efficiency. */

Going a bit advanced, you can also use codeToTokens or codeToHast to get the intermediate data structure, and render them by yourself:

ts twoslash theme:min-dark
// @twoslash-cache: {"v":1,"hash":"b815da6ae7b98fc8742ecce6129ff09de56b53a6198bbf341d9bfeceb03a3cda","data":"N4Igdg9gJgpgziAXAbVAFwJ4AcZJACwgDcYAnEAGhDRgA808AKAQwBsBLZuASgAIBjCGDhoB0GABUIUgNYxhiXo0GxFI0uzABzCrwhY07IXEUBhcVNny4AeQNHhAHgBCAVzBRWMKABlm212YtGF03Dy8oCXwYAFsYAD4+AF543gAFUggY9jgYRythACV4V1Y0eIAdMHYYrAhSURVJaQg5YUoQEWYGpABOKi9tNHwkAEYAJio0buCGRBAmy1brDo4wXEQABip+fG7mfhpyRF6AXwp0bA2CYjIOmno8QWFRNGWFXijY7wLkAF1/h0oBB+Ah5uMACK8bqkZgYPQAM14bzacF0CPYpBEvCgNWsDl4OV4a3gulyzygOLxwgJRJR1kJYGhxM0MAAdB0uj1EAAWABsA3kWmGSAF1BmMDm1HeCAGrKQkwWe1hhzuiAAHOdLjg8IQSOQpnQpSwOFw+IsWqjlOI1GgNNpdPpDMYzBZLdY7M6nGFPN4/AEgiFeD6Il84olFBksjk8gU4MU4KVylUanUGmJYEtUZzpty+QBmQVDEaICZTCVSi1x1byxCK3b7VXHcYAVi11CuutuBuoRqYWEyOAaGD4rH8WgA/IoQ37x4FgrwAD68ADKOH4nFY/q085gS947lgGPWUBz3Tmo1G2xAg2FJZ55dIszwY+0NfWSELSsbRwV7cwOrzHqdyGo88yMAO+hkJgfDmLAnoOHAK6aFoXhhnkM6RNE4ZssM3yKOhxRaDkdrMF6ACCYDwsuK52ihPjsEcbAAKrVEILiHqG2FBuoKHxGe3ITGKt4iogLaPs+8x4XE74bA+34qr+danH8OziHgqb1KIwAZs0ca8KcvAIpkMS8AA5HA+DsDI7BmVUVTPNiOn0sIBm8Ek0IAO7MIxulZtYjBmY4uJEAIY5wHASQVCACIQBA0XxAARt0jgAPQhfEZm6MAVS8MS46KGZ+BoDErBZblyLcYV2RgAAtFA3QyHZYCnNwHRxNMSCgEa+JCHgaAIKcpxAA="}
import { codeToTokens } from 'shiki'

const { tokens } = await codeToTokens('<div class="foo">bar</div>', {
  lang: 'html',
  theme: 'min-dark'
})
ts twoslash theme:catppuccin-mocha
// @twoslash-cache: {"v":1,"hash":"c2d38cc11de83aa2f1d11651d47d604c307a1a81460c52619a8483484bbcdeb6","data":"N4Igdg9gJgpgziAXAbVAFwJ4AcZJACwgDcYAnEAGhDRgA808AKAQwBsBLZuASgAIBjCGDhoB0GABUIACS5pEvRoNgKRpdmADmFXhCxp2QuAoDC4qbJEB5fYeEAeAEIBXMFFYwoAGWZbnzTRgdFzcPKAl8GABbGAA+PgBeWN4ABVIIKPY4GHsAJQgINFiAHTB2KKwIUlFlSRk5ShARZmqkAE4qDy00fCQARgAGKjQWwIZEEFqLBs6NXEQhyfwW5n4ackQ2gF8KdGx5gmIyRpp6PEFhUWWRBXzCxubWxAAWACZOmG7el+HRmHGCDMQBwwPN3ksVmtjogAGw7PY4PCEEjkYZ0AEsDhcPhTeoiJTiVRodRaHR6AxGUzmPFoGwUhwhdyeHx+AJBXiMsIRaJxbgKNIZLI5O5FUrlSrVMSwaYiB4jJ4AVgVHy+/UW8rG52plgYs1BSHB/GWpFW6362121H2SKOqOo6KYWHSOGqGD4rF8mgUxUmcDgPrlLXGbQ6wM+mh6SGev1ImomHq0jRB8wAzFQjZCzYhXvCrYiJsjjmizhNGE69GRMHwzLA6XY4ABlDSaDzcmJOVxM8KRGKxAB0PR5CjbMFyME0WWJzHpAEEwBheAAfXgN4nNrzsdZsACqZSEHdCnhHOjUzdigaefT60bDqsQyuofwBg5iSbmUfTxtN0JzAF10+IeDilUojAFKdQ6rwWy8AAZukUS8AA5HA+DsAA1uwiGlKUFwiLw1yiAkvDMAA7swm7gTKaCMIhA7ogAtKQni8GBgisFUChMVAADcUGITowClLwvAJl6SH8H6/FCbwL4wAoiH8NOWBYM4/D8Bo9FRBAGZYWAWzcI0MQjEgoDop8cB2HgaAIFsWxAA=="}
import { codeToHast } from 'shiki'

const hast = await codeToHast('.text-red { color: red; }', {
  lang: 'css',
  theme: 'catppuccin-mocha'
})

Highlighter Usage

The shorthands we provided are executed asynchronously as we use WASM and load themes and languages on demand internally. In some cases, you may need to highlight code synchronously, so we provide the createHighlighter function to create a highlighter instance that can later be used synchronously.

The usage is pretty much the same as with codeToHtml, where each theme and language file is a dynamically imported ES module. It would be better to list the languages and themes explicitly to have the best performance.

text
/* Detailed source-code truncated for AI context efficiency. */

:::info Important Note
Highlighter instance should be long-lived singleton. You might need to cache it somewhere and reuse it across your application. Avoid calling createHighlighter in hot functions or loops.

If running on Node.js, we recommend using the Shorthands which manages the highlighter instance and dynamic theme/language loading for you.
:::

Additionally, if you want to load themes and languages after the highlighter is created, you can use the loadTheme and loadLanguage methods.

ts twoslash
// @twoslash-cache: {"v":1,"hash":"8ce2944f34901319a707c331109f0ab6777c21709e2685cf9e4136e2d0985b06","data":"N4Igdg9gJgpgziAXAbVAFwJ4AcZJACwgDcYAnEAGhDRgA808BjCMONAAnwEsBzfAG174apROwASQwXxEBxGGDJdGAHgBCAVzBR+MKABkAhmB4bDPGBXabtuqABV8MALYwAfJRBtDpBogCsAAxUuiZo+EgAjJFUaD4WfgRSQiKegopRVIz4PoaMqYgAbAC+FOjYuIgExGSeNPR4ABRYpBA4vhgAlOwAytwA1lwAkmAiYIb86lo6ekYmZhZWNjMOTq5uAHT8EIarLjBijRvH4ftwh8t2jvvsAD7s164jWBoc9wA64Cwwn53IALrdAC8bnYAAVWs4uHAYCoiBAuFAPFQoBBGAgqvodlB2IZ2KdXPiIPinJxkjIyFY4MT+IYROwuBxGMZ2AAjGDsDQwnFwDBgbKtSBc/gYDaeby+JCFABMIQUPHCSAAnLF4jBEttdo9cCEuBlEDEQNlcvlagaABylco4PCEEjkWJ0RLMVgcbh8aTCMhiSQelJkeSKUjKKa2WbGUzmSzWaZXNbucVxSWIJX+OVhCIGw1JhK28leh0gdKVWVGnKkPIFEplagVW01Qv1RLNVrtTDdPpcQYjMYTUMrOaRxYxsN7dZbbGDhYHdhHY60kznWeXcPzKN3dhTqPPV4bno4RhcCZbix/QHsEHgyHQ2HwxHIkCo9F4LG7XHsBdDjloYmnMl+ilSCpGk6TIBkmRZdlOW5dheX5fBBQgYVRUTHw/EiQIAHZ0wVTNIlLHN1TwTUDAjac0j1EssnLSszUiS1/iyaBKhAAB6ViP2xEkzlxbQPzIqM4FxAAzelGFIGA6S4Fh3nGAB3QxGX/AR/VICctXjRoAHIiEZeAYQAWk9NAtM6WTDAUpT3RUwD1NItcLG09E4FMzxXDiJBQCdBQ4GksA8DQBBimKIA=="}
import { createHighlighter } from 'shiki'

const highlighter = await createHighlighter({ themes: [], langs: [] })
// ---cut---
// load themes and languages after creation
await highlighter.loadTheme('vitesse-light')
await highlighter.loadLanguage('css')

Since Shiki v1.0, it requires all themes and languages to be loaded explicitly.

text
/* Detailed source-code truncated for AI context efficiency. */

If you want to load all themes and languages (not recommended), you can iterate over all keys from bundledLanguages and bundledThemes.

text
/* Detailed source-code truncated for AI context efficiency. */

Fine-grained Bundle

When importing shiki, all the themes and languages are bundled as async chunks. Normally it won't be a concern to you as they are not being loaded if you don't use them. In some cases, if you want to control what to bundle, you can use the core and compose your own bundle.

Check out the Fine-grained Bundle section for more details.

Bundle Presets

We also provide some pre-composed bundles for you to use easily, you can learn more about them in the bundles section.

CJS Usage

shiki is published as ESM-only to reduce the package size. It's still possible to use it in CJS, as Node.js supports importing ESM modules dynamically in CJS.

For example, the following ESM code:

text
/* Detailed source-code truncated for AI context efficiency. */

Can be written in CJS as:

text
/* Detailed source-code truncated for AI context efficiency. */

CDN Usage

To use shiki in the browser via CDN, you can use esm.run or esm.sh.

html theme:rose-pine
<body>
  <div id="foo"></div>

  <script type="module">
    // be sure to specify the exact version
    import { codeToHtml } from 'https://esm.sh/[email protected]'
    // or
    // import { codeToHtml } from 'https://esm.run/[email protected]'

    const foo = document.getElementById('foo')
    foo.innerHTML = await codeToHtml('console.log("Hi, Shiki on CDN :)")', {
      lang: 'js',
      theme: 'rose-pine'
    })
  </script>
</body>

It's quite efficient as it will only load the languages and themes on demand. For the code snippet above, only four requests will be fired (shiki, @shikijs/themes/vitesse-light, @shikijs/langs/javascript, shiki/wasm.mjs), with around 200KB data transferred in total.

Demo

Cloudflare Workers

Cloudflare Workers does not support initializing WebAssembly from binary data, so the default wasm build won't work. You need to upload the wasm as assets and import it directly.

Meanwhile, it's also recommended to use the Fine-grained Bundle approach to reduce the bundle size.

text
/* Detailed source-code truncated for AI context efficiency. */

Guide Load Lang (`docs/guide/load-lang.md`)

Load Custom Languages

See All Builtin Languages first.

You can load custom languages by passing a TextMate grammar object into the langs array.

text
/* Detailed source-code truncated for AI context efficiency. */

You can also load languages after the highlighter has been created.

text
/* Detailed source-code truncated for AI context efficiency. */

Migrate from v0.14

Since v1.0, shiki now is environment agnostic, we don't have access to the file system. That means the path property [email protected] supports is not available in v1.0, and you must to read the files yourself and pass in the object.

For example, the following would not work:

ts
const highlighter = await createHighlighter({
  langs: [
    {
      name: 'vue-vine',
      scopeName: 'source.vue-vine',
      // ‼️ This would not work!
      path: join(__dirname, './vine-ts.tmLanguage.json'),
      embeddedLangs: [
        'vue-html',
        'css',
        'scss',
        'sass',
        'less',
        'stylus',
      ],
    },
  ],
  themes: []
})

Instead, load that file yourself (via fs, import(), fetch(), etc.):

ts
const vineGrammar = JSON.parse(fs.readFileSync(join(__dirname, './vine-ts.tmLanguage.json'), 'utf8'))

const highlighter = await createHighlighter({
  langs: [
    {
      name: 'vue-vine',
      scopeName: 'source.vue-vine',
      embeddedLangs: [
        'vue-html',
        'css',
        'scss',
        'sass',
        'less',
        'stylus',
      ],
      ...vineGrammar
    },
  ],
  themes: []
})

Custom Language Aliases

You can register custom language aliases with the langAlias option. For example:

text
/* Detailed source-code truncated for AI context efficiency. */

Guide Load Theme (`docs/guide/load-theme.md`)

Load Custom Themes

See All Builtin Themes first.

You can load custom themes by passing a Theme object into the themes array.

text
/* Detailed source-code truncated for AI context efficiency. */

You can also load themes after the highlighter has been created.

text
/* Detailed source-code truncated for AI context efficiency. */

The theme is a TextMate theme in JavaScript object. For example, it should look like this.


Guide Migrate (`docs/guide/migrate.md`)


outline: deep

Migration

We suggest you to migrate step by step, following each version's migration guide.

Migrate from v3.0

If you are on v3.0, v4.0 only drops support for Node.js 18 and remove deprecated APIs, so you should be able to directly bump to v4.0. Read Shiki v4.0 for more details.

Migrate from v2.0

If you are on v2.0 and there is no warning in your usage, you should be able to directly bump to v3.0, read Shiki v3.0 for more details.

Migrate from v1.0

We recommend you to migrate to v2.0 first, then to v3.0.

Migrate from v0.14

The v1.0 release of Shiki is a major rewrite that we took the chance to revise every design decision we made in the past. We originally had a separate package name as Shikiji to experiment with the new design, now it's merged back to Shiki as v1.0.

[!TIP] Learn more
Interested in the story behind v1.0? Check out this blog post for more details.

Compare to [email protected], the list of breaking changes are:

Hard Breaking Changes

Breaking changes that you need to migrate manually:

  • CJS and IIFE builds are dropped. See CJS Usage and CDN Usage for more details.
  • codeToHtml uses hast internally. The generated HTML will be a bit different but should behave the same.
  • css-variables theme is no longer supported. Use the dual themes approach instead, or learn more at the Theme Colors Manipulation page.

Soft Breaking Changes

Breaking changes applies to main package shiki, but are shimmed by the compatible build @shikijs/compat:

  • Top-level named exports setCDN, loadLanguage, loadTheme, setWasm are dropped as they are not needed anymore.
  • BUNDLED_LANGUAGES, BUNDLED_THEMES are moved to @shikijs/langs and @shikijs/themes and renamed to bundledLanguages and bundledThemes respectively.
  • theme option for createHighlighter is dropped, use themes with an array instead.
  • Highlighter does not maintain an internal default theme context. theme option is required for codeToHtml and codeToTokens.
  • codeToThemedTokens is renamed to codeToTokensBase, a higher level codeToTokens is added.
  • codeToTokens sets includeExplanation to false by default.
  • .ansiToHtml is merged into .codeToHtml as a special language, ansi. Use .codeToHtml(code, { lang: 'ansi' }) instead.
  • lineOptions is dropped in favor of the fully customizable transforms option.
  • LanguageRegistration's grammar field is flattened to LanguageRegistration itself, refer to the types for more details.

Ecosystem Packages

  • shiki-twoslash has been completely rewritten. It's no longer a wrapper around Shiki highlighter, but instead, it's now a Shiki transformer that can be plugged in any integrations that supports SHiki transformers. The package is now @shikijs/twoslash.
  • Integrations of shiki-twoslash, like gatsby-remark-shiki-twoslash etc, will be slowly moved to a general Shiki version. Before that, you can use @shikijs/rehype or @shikijs/markdown-it to integrate Shiki into those meta-frameworks.
  • New official integrations like @shikijs/monaco, @shikijs/cli, @shikijs/rehype, @shikijs/markdown-it are introduced.
  • shiki-renderer-path and shiki-renderer-svg packages are being deprecated due to low usage. If need them, please open an issue with your use case, we are open to bring them back.
  • vuepress-plugin-shiki is deprecated as VuePress is no longer recommended. Its successor VitePress has a built-in Shiki integration.

Migrate from Shikiji

If you are already using Shikiji, first make sure you are on the latest minor v0.10. Then the migration should be very straightforward by renaming the packages:

  • shikiji -> shiki
  • shikiji-core -> @shikijs/core
  • shikiji-twoslash -> @shikijs/twoslash
  • shikiji-transformers -> @shikijs/transformers
  • shikiji-monaco -> @shikijs/monaco
  • shikiji-cli -> @shikijs/cli
  • markdown-it-shikiji -> @shikijs/markdown-it
  • rehype-shikiji -> @shikijs/rehype

Guide Regex Engines (`docs/guide/regex-engines.md`)


outline: deep

RegExp Engines

TextMate grammars are based on regular expressions that match tokens. More specifically, they assume that Oniguruma (a powerful regex engine written in C) will be used to interpret the regular expressions. To make this work in JavaScript, we compile Oniguruma to WebAssembly to run in the browser or Node.js.

Shiki also offers the ability to switch the regex engine or provide a custom implementation. To do so, add an engine option to createHighlighter or createHighlighterCore. For example:

ts
import { createHighlighter } from 'shiki'

const shiki = await createHighlighter({
  themes: ['nord'],
  langs: ['javascript'],
  engine: { /* custom engine */ }
})

Shiki comes with two built-in engines:

Oniguruma Engine

This is the default engine that uses the compiled Oniguruma WebAssembly.

ts
import { createHighlighter } from 'shiki'
import { createOnigurumaEngine } from 'shiki/engine/oniguruma'

const shiki = await createHighlighter({
  themes: ['nord'],
  langs: ['javascript'],
  engine: createOnigurumaEngine(import('shiki/wasm'))
})

JavaScript RegExp Engine

This engine uses JavaScript's native RegExp. Since regular expressions used by TextMate grammars are written for Oniguruma, we use Oniguruma-To-ES to transpile Oniguruma patterns to native JavaScript regexes.

ts {2,4,9}
import { createHighlighter } from 'shiki'
import { createJavaScriptRegexEngine } from 'shiki/engine/javascript'

const jsEngine = createJavaScriptRegexEngine()

const shiki = await createHighlighter({
  themes: ['nord'],
  langs: ['javascript'],
  engine: jsEngine
})

const html = shiki.codeToHtml('const a = 1', { lang: 'javascript', theme: 'nord' })

The advantage of using the JavaScript engine is that it doesn't require loading a large WebAssembly file for Oniguruma. It is also faster for some languages, since the regular expressions run as native JavaScript.

Please check the compatibility table for the support status of languages you are using. As of Shiki 3.9.1, all built-in languages are supported.

::: info
The JavaScript engine is best when running in the browser and in cases when you want to control the bundle size. If you run Shiki on Node.js (or at build time) and bundle size or WebAssembly support is not a concern, the Oniguruma engine ensures maximum language compatibility.
:::

Use with Unsupported Languages

Unlike the Oniguruma engine, the JavaScript engine is strict by default. It will throw an error if it encounters an invalid Oniguruma pattern or a pattern that it cannot convert. If you want best-effort results for unsupported grammars, you can enable the forgiving option to suppress any conversion errors:

ts
const jsEngine = createJavaScriptRegexEngine({ forgiving: true })
// ...use the engine

This can result in highlighting mismatches, so check your results.

JavaScript Runtime Target

For best results, the JavaScript engine uses the RegExp v flag, which is available in Node.js v20+ and ES2024 (browser compatibility). For older environments, it automatically uses the u flag instead, but this results in a few less grammars being supported.

By default, the runtime target is automatically detected. You can override this behavior by setting the target option:

ts
const jsEngine = createJavaScriptRegexEngine({
  target: 'ES2018', // or 'auto' (default), 'ES2024', 'ES2025'
})

Pre-compiled Languages

Instead of compiling regular expressions on-the-fly, we also provide pre-compiled languages for the JavaScript engine to further reduce startup time.

::: warning
Pre-compiled languages are not yet supported, due to a known issue that affects many languages. Please use with caution.
:::

::: info
Pre-compiled languages require support for RegExp UnicodeSets (the v flag), which requires ES2024 or Node.js 20+, and may not work in older environments. Can I use.
:::

You can install them with @shikijs/langs-precompiled, and change your @shikijs/langs imports to @shikijs/langs-precompiled:

ts
import { createHighlighterCore } from 'shiki/core'
import { createJavaScriptRawEngine } from 'shiki/engine/javascript'

const highlighter = await createHighlighterCore({
  langs: [
    import('@shikijs/langs/javascript'), // [!code --]
    import('@shikijs/langs/typescript'), // [!code --]
    import('@shikijs/langs-precompiled/javascript'), // [!code ++]
    import('@shikijs/langs-precompiled/typescript'), // [!code ++]
    // ...
  ],
  themes: [
    import('@shikijs/themes/nord'),
  ],
  engine: createJavaScriptRegexEngine(), // [!code --]
  engine: createJavaScriptRawEngine(), // [!code ++]
})

If you are not using custom grammars that require transpilation, you can use createJavaScriptRawEngine to skip the transpilation step, further reducing bundle size.

If you are using shiki-codegen, you can generate pre-compiled languages with the --precompiled and --engine=javascript-raw flags.


Guide Shorthands (`docs/guide/shorthands.md`)

Shorthands

The easier way to get started with shiki is to use the provided shorthand functions. These will load the necessary themes and languages on demand, and automatically cache them in memory. Different from createHighlighter and createHighlighterCore, the operations are asynchronous.

text
/* Detailed source-code truncated for AI context efficiency. */

Create Shorthands with Fine-Grained Bundles

You can create your own shorthands with fine-grained bundles. Here is an example of creating shorthands with fine-grained bundles:

ts
import { createBundledHighlighter, createSingletonShorthands } from 'shiki/core'
import { createJavaScriptRegexEngine } from 'shiki/engine/javascript'

const BundledLanguage = {
  typescript: () => import('@shikijs/langs/typescript'),
  javascript: () => import('@shikijs/langs/javascript'),
  vue: () => import('@shikijs/langs/vue'),
}

const BundledTheme = {
  'light-plus': () => import('@shikijs/themes/light-plus'),
  'dark-plus': () => import('@shikijs/themes/dark-plus'),
}

// This creates your custom 'createHighlighter' function with fine-grained bundles
export const createHighlighter = /* @__PURE__ */ createBundledHighlighter<
  BundledLanguage,
  BundledTheme
>({
  langs: bundledLanguages,
  themes: bundledThemes,
  engine: () => createJavaScriptRegexEngine(),
})

// This creates the shorthands for you
export const {
  codeToHtml,
  codeToHast,
  codeToTokensBase,
  codeToTokens,
  codeToTokensWithThemes,
  getSingletonHighlighter,
  getLastGrammarState,
} = /* @__PURE__ */ createSingletonShorthands(
  createHighlighter,
)

You can also use shiki-codegen to generate the fine-grained bundles for you.


Guide Sync Usage (`docs/guide/sync-usage.md`)

Synchronous Usage

The await createHighlighter() and highlighter.codeToHtml() are already the effort to do the separations of asynchronism and synchronism. For most of the cases, you should be able to resolve the async part in the initialization phase and use the highlighter synchronously later.

In some extreme cases that you need to run Shiki completely synchronously, since v1.16, we provide a synchronous version of the core API. You can use createHighlighterCoreSync to create a highlighter instance synchronously.

ts
import js from '@shikijs/langs/javascript'
import nord from '@shikijs/themes/nord'
import { createHighlighterCoreSync } from 'shiki/core'
import { createJavaScriptRegexEngine } from 'shiki/engine/javascript'

const shiki = createHighlighterCoreSync({
  themes: [nord],
  langs: [js],
  engine: createJavaScriptRegexEngine()
})

const html = shiki.highlight('console.log(1)', { lang: 'js', theme: 'nord' })

When doing so, it requires all themes and langs to be provide as plain objects. Also an explicit engine is required to be provided. With the new JavaScript RegExp engine you are able to create an engine instance synchronously as well.

The Oniguruma Engine can only be created asynchronously, so you need to resolve the engine promise before creating the sync highlighter.

ts
import js from '@shikijs/langs/javascript'
import nord from '@shikijs/themes/nord'
import { createHighlighterCoreSync } from 'shiki/core'
import { createOnigurumaEngine } from 'shiki/engine/oniguruma'

// Load this somewhere beforehand
const engine = await createOnigurumaEngine(import('shiki/wasm'))

const shiki = createHighlighterCoreSync({
  themes: [nord],
  langs: [js],
  engine, // if a resolved engine passed in, the rest can still be synced.
})

const html = shiki.highlight('console.log(1)', { lang: 'js', theme: 'nord' })

Guide Theme Colors (`docs/guide/theme-colors.md`)

Theme Colors Manipulation

Arbitrary Color Values

Usually, TextMate themes expect the color values of each token to be a valid hex color value. This limitation is inherited from vscode-textmate. However, in Shiki v0.9.15 we introduced an automatic workaround by replacing non-hex color values with a placeholder and replacing them back on tokenization. This would allows you to use themes with arbitrary color values for rendering without worrying about the technical details:

text
/* Detailed source-code truncated for AI context efficiency. */

::: info Notice
Use this carefully as this will diverge from TextMate theme compatibility.

This may make the theme incompatible with non-web usage such as shiki-cli and shiki-monaco.
:::

Learn more about how to load themes.

Color Replacements

You can also use the colorReplacements option to replace the color values of the theme. This is useful when you want to use a theme with a different color palette. It can be provided on both the theme object and the codeToHast codeToHtml options.

The colorReplacements object should follow a color-to-color format, where the keys represent the color to be replaced and the value represents the new color:

js
const html = await codeToHtml(
  code,
  {
    lang: 'js',
    theme: 'min-dark',
    colorReplacements: {
      '#ff79c6': '#189eff'
    }
  }
)

In addition, colorReplacements may contain scoped replacements. This is useful when you provide multiple themes and want to replace the colors of a specific theme:

js
const html = await codeToHtml(
  code,
  {
    lang: 'js',
    themes: { dark: 'min-dark', light: 'min-light' },
    colorReplacements: {
      'min-dark': {
        '#ff79c6': '#189eff'
      },
      'min-light': {
        '#ff79c6': '#defdef'
      }
    }
  }
)

This is only allowed for the colorReplacements option and not for the theme object.

CSS Variables Theme

Shiki provides a factory function helper createCssVariablesTheme for creating a theme that uses CSS variables easier. Note that this theme is a lot less granular than most of the other themes and requires to define the CSS variables in your app. This is provided for easier migration from Shiki's css-variables theme. For better highlighting result, we recommend construct the theme manually with Arbitrary Color Values or use Color Replacements to override an existing theme.

This theme is not included by default and must be registered explicitly:

text
/* Detailed source-code truncated for AI context efficiency. */

CSS variables example:

text
/* Detailed source-code truncated for AI context efficiency. */

If you are migrating from Shiki v0, some variables are renamed:

Shiki v0 Shiki v1.0
--shiki-color-text --shiki-foreground
--shiki-color-background --shiki-background
--shiki-color-ansi-* --shiki-ansi-*

Guide Transformers (`docs/guide/transformers.md`)

Transformers

Shiki uses hast, a AST format for HTML, to process the result and generate the HTML.

You can provide your own transformers to customize the generated HTML by manipulating the hast tree. You can pass custom functions to modify the tree for different types of nodes. For example:

text
/* Detailed source-code truncated for AI context efficiency. */

We also provide some common transformers for you to use, see @shikijs/transformers and @shikijs/colorized-brackets for more details.

Transformer Hooks

mermaid
flowchart LR
  preprocess --> tokens
  tokens --> span
  span --> span
  span --> line
  line --> span
  line --> code
  code --> pre
  pre --> root
  root --> postprocess
  • preprocess - Called before the code is tokenized. You can use this to modify the code before it is tokenized.
  • tokens - Called after the code is tokenized. You can use this to modify the tokens.
  • span - Called for each `` tag, for each token.
  • line - Called for each line `` tag.
  • code - Called for each <code> tag, wraps all the lines.
  • pre - Called for each `` tag, wraps the <code> tag.
  • root - The root of HAST tree. Usually with only one child `` tag.
  • postprocess - Called after the HTML is generated, get a chance to modify the final output. Will not be called in codeToHast.

Meta

Transformers can also access markdown 'meta' strings in supported integrations.

markdown
```html foo=bar baz-qux="qu ux"

You can access the raw meta using:

ts
this.options.meta
// => { __raw: 'foo=bar baz-qux="qu ux"' }

Enforcing Transformer Ordering

For compatibility or fine-grained control, it may be necessary to enforce the order of a Shiki transformer. You can control the position of a transformer with the enforce modifier:

  • pre: invoked before normal transformers
  • default: invoked as a normal transformers
  • post: invoked after normal transformers

Example:

ts twoslash
// @twoslash-cache: {"v":1,"hash":"8d7b272af17166c83782bdb0d9e41de0f75c878526f69245c6e6021e1a4bfa45","data":"N4Igdg9gJgpgziAXAbVAFwJ4AcZJACwgDcYAnEAGhDRgA808AKAQwBsBLZuASgAJ2wNUgDNmAYxi8AyvnYBrdgBVSzMHGERSAWzIAdMOy1ZNaabIXLV6zTvJU4aZqQaIAjABYqrGGADmafCRXADYqR1JfGBcQGXklFTUNbTJKEA4wXEQABioxfCdxISD3AF8KdGxMgmIUsLposQg1UzEAVwcILUtEmzJEMzju62S7EAcnFwBWAGYvH39AtwB2MKdIhva0TqGk21T0zIAmXPyVMSLEYLKKnDxCElGaeiY2Th5+QTJRCQGLBOHbPpDMZnL94lZdrUxuEXEtjml5gEgqFqGsonhYn8Ib1RgckPC8gVzilEIdJtdqJU7jVHvUmFhSBAcM4MHxMeCeiMAHRgZg6AD8/QcpAEvl4AB9eK0wLBhAIYFBUlAIGIEIgQAA5PmSCDCXgBSRof6Q0bjZxIACckzmfiRiE8qIi6PVvJ0+3lSFmIEJZwuhwpmFu6vuUKe0UYDKZZEwbPMHIBZC5PiSEkFvF0IAZMAzEvTmYgDhzkulsvliqoytVeAAomAU5IsKxWr4BB8iCrmGh2E19ewyLw4IZ2KwnPqILwAO4wABGWHEcl4rAgzFgpDgXN4AAkIBA5HBeJpV6KgfuHMPWAPWtOAFYwc5j/X4SQAA0PZGfvEjzMwH0fkkIu4Hjed5oFy+j6AACk2LZgG2HZdj2b6kIg+gALS8M+yaaBI/QAORZrhH6Ns2AhwGhvCQNobCftBpHkZhdbYTAeHGA4hE0SRajkXAcafgWpjETBCD2DCQSHK4NoLEgKyOuseBYaQEjuhkSAOj6hQkv65SUkG1QPKkYb0oy36smCOw4lyjSwGmjCMAE7BwP07LmSMADCTRhhQvD5A4/TVt4OiCHwAC8AB8vD+TAgWmJK7bsFAfDFjKMByhk5YgJWaogC5Wh/hhAA8VkwKFH4wAFPigbwABKUStKQsHMBRMATrwGrQJIE7nrwpAwI24iGk+B4ijB1FNDAXKpGaLiuNMFqSXaDrhHJ6pFcpmTWt6pwaeQpIBlSwY0gZdLqhGBQ6EIfCQLAfnlYIk2iW47gOt4tqLItaLRFduBeB6iAbepxI7UsJQALq5O1eDAiY+qVLwwBmcaOK8CUvDCIyuW4TxcS4eBYCNM0vBtB0XSIyMTlxjl/bBXD+i8BR2p4VoGCoUa2IjLhFC07wCk4bw+E9RzXNFYwX18MAXN0wA9JLvBcnLXNlPoJSpOdzBIKA9Q+IOTR4GgCAlCUQA==="}
import type { ShikiTransformer } from 'shiki'

const customTransformer: ShikiTransformer = {
  name: 'my-transformer',
  enforce: 'pre',
  code(node) {
    // ...
  },
}

Packages Astro (`docs/packages/astro.md`)

Astro Integration

You don't need explicit integration in Astro as Astro already uses Shiki under the hood.

Learn more about the configurations in the Astro documentation.


Packages Cli (`docs/packages/cli.md`)

@shikijs/cli

Shiki in the command line.

Usage

The Shiki CLI works like cat command, but with syntax highlighting.
It also supports remote files.

bash
npx @shikijs/cli README.md
bash
npx @shikijs/cli \
  'https://github.com/shikijs/shiki/blob/main/taze.config.ts?raw=true'

Install

You can also install it globally. Command aliases @shikijs/cli, shiki, skat are registered.

::: code-group

sh [npm]
npm i @shikijs/cli
sh [yarn]
yarn global add @shikijs/cli
sh [pnpm]
pnpm add @shikijs/cli
sh [bun]
bun add -g @shikijs/cli
sh [deno]
deno install -gREn skat npm:@shikijs/cli

:::

sh
skat src/index.ts

Options

`--theme`

Specify the theme to use. Defaults to vitesse-dark.

bash
npx @shikijs/cli README.md --theme=nord

`--lang`

Language is auto-inferred from the file extension. You can override it with --lang.

bash
npx @shikijs/cli src/index.js --lang=ts

`--format`

Specify the output format. Defaults to ansi.
Supported values: ansi, html.

bash
npx @shikijs/cli README.md --format=html

Node.js API

The @shikijs/cli package also provides a Node.js API.

::: code-group

sh [npm]
npm i @shikijs/cli
sh [yarn]
yarn add @shikijs/cli
sh [pnpm]
pnpm add @shikijs/cli
sh [bun]
bun add @shikijs/cli
sh [deno]
deno add npm:@shikijs/cli

:::

`codeToANSI`

The asynchronous codeToANSI function allows you to convert code to ANSI escape codes for terminal output.
This is useful for rendering syntax-highlighted code in the terminal.

ts
import { codeToANSI } from '@shikijs/cli'

const highlighted = await codeToANSI(source, 'typescript', 'nord')

console.log(highlighted)

codeToANSI takes three required parameters:

  1. code: string
  2. lang: BundledLanguage
  3. theme: BundledTheme

and one optional parameter:

  1. options?: CodeToANSIOptions

The color level of the emitted escape codes is explicit instead of being inferred from the environment, and defaults to 3 (truecolor). Use options.colorLevel to target a different level — pass the level reported by ansis to follow the current terminal, or 0 to get plain text:

ts
import { codeToANSI } from '@shikijs/cli'
import ansis from 'ansis'

const highlighted = await codeToANSI(source, 'typescript', 'nord', {
  colorLevel: ansis.level,
})

Packages Codegen (`docs/packages/codegen.md`)

shiki-codegen

Codegen for Shiki to create optimized bundles dedicated for your usage.

Usage

CLI

bash
npx shiki-codegen \
  --langs typescript,javascript,vue \
  --themes light-plus,dark-plus \
  --engine javascript \
  ./shiki.bundle.ts

File shiki.bundle.ts will be created with the code that you can use in your project.

Then you can use it in your project:

ts
import { codeToHtml } from './shiki.bundle'

const html = await codeToHtml(code, { lang: 'typescript', theme: 'light-plus' })

Programmatic

You can also use shiki-codegen programmatically, and write the generated code to a file:

ts
import { codegen } from 'shiki-codegen'

const { code } = await codegen({
  langs: ['typescript', 'javascript', 'vue'],
  themes: ['light-plus', 'dark-plus'],
  engine: 'javascript',
  typescript: true
})

// Write the code to a file

Packages Colorized Brackets (`docs/packages/colorized-brackets.md`)


outline: deep

@shikijs/colorized-brackets

VSCode-style colorized brackets transformer for Shiki.

Install

::: code-group

sh [npm]
npm i -D @shikijs/colorized-brackets
sh [yarn]
yarn add -D @shikijs/colorized-brackets
sh [pnpm]
pnpm add -D @shikijs/colorized-brackets
sh [bun]
bun add -D @shikijs/colorized-brackets
sh [deno]
deno add npm:@shikijs/colorized-brackets

:::

Usage

Add to your Shiki transformers:

ts colorize-brackets
import { transformerColorizedBrackets } from '@shikijs/colorized-brackets'
import { codeToHtml } from 'shiki'

const html = await codeToHtml('let values: number[] = [];', {
  lang: 'ts',
  theme: 'dark-plus',
  transformers: [transformerColorizedBrackets()],
})

Colors

Brackets are automatically colored according to your Shiki theme (or themes if using dual themes), with support for all of Shiki's built-in themes. However, you can customize colors if you've added custom themes to Shiki, or if you want to override the colors of a built-in theme:

ts colorize-brackets
const html = await codeToHtml('let values: number[] = [];', {
  lang: 'ts',
  theme: myCustomTheme,
  transformers: [transformerColorizedBrackets({
    themes: {
      'my-custom-theme': ['goldenrod', 'blueviolet', 'dodgerblue', 'crimson'],
    },
  })],
})

The final color is the mismatched bracket color. The other colors are for each "level" of bracket pair. Any valid CSS color can be used.

If no bracket colors are found for a theme, it falls back to the default dark-plus theme.

Brackets

You can customize the bracket pairs:

ts colorize-brackets
const transformer = transformerColorizedBrackets({
  bracketPairs: [{ opener: '{', closer: '}' }],
})

The above would only colorize {} curly brackets. The default config colorizes [] square brackets, {} curly brackets, () parentheses, and <> angle brackets (only in TS type annotations).

For advanced usage, you can specify which TextMate scopes a bracket pair is allowed or denied in, using scopesAllowList and scopesDenyList. For example, the default config for <> angle brackets is:

ts colorize-brackets
const bracketPair = {
  opener: '<',
  closer: '>',
  scopesAllowList: [
    'punctuation.definition.typeparameters.begin.ts',
    'punctuation.definition.typeparameters.end.ts',
  ],
}

Language-specific Overrides

All settings can be overridden for specific languages using the langs option:

ts colorize-brackets
const transformer = transformerColorizedBrackets({
  langs: { ts: myCustomTypescriptConfig },
})

Explicit Trigger

If you do not want colorized brackets for all code blocks, you can enable the explicitTrigger option:

ts colorize-brackets
const transformer = transformerColorizedBrackets({
  explicitTrigger: true,
})

Then, only code blocks with the colorize-brackets meta string will have bracket colorizing enabled.

md
```ts
// no bracket colorizing
```

```ts colorize-brackets
// brackets will be colorized
```

Packages Magic Move (`docs/packages/magic-move.md`)


outline: deep

@shikijs/magic-move

Smoothly animated code blocks with Shiki. Useful for code-step animations in slide decks (e.g. Slidev) and tutorials.

@shikijs/magic-move is a low-level library: at its core is a framework-agnostic diff/animation machine and renderer, with thin wrappers for Vue, React, Solid, Svelte, and a Web Component.

Each framework wrapper provides three components:

  • ShikiMagicMove — the main component; wraps a code block and animates whenever code changes
  • ShikiMagicMovePrecompiled — animations for compiled tokens, without the Shiki dependency at runtime
  • ShikiMagicMoveRenderer — the low-level renderer component

ShikiMagicMove needs a Shiki highlighter instance plus the bundled stylesheet (@shikijs/magic-move/style.css). Whenever code changes, the component animates the diff.

Install

::: code-group

sh [npm]
npm i -D @shikijs/magic-move shiki
sh [yarn]
yarn add -D @shikijs/magic-move shiki
sh [pnpm]
pnpm add -D @shikijs/magic-move shiki
sh [bun]
bun add -D @shikijs/magic-move shiki
sh [deno]
deno add npm:@shikijs/magic-move npm:shiki

:::

Usage

Vue

Import @shikijs/magic-move/vue and pass the highlighter instance to the ShikiMagicMove component.

vue
<script setup>
import { ShikiMagicMove } from '@shikijs/magic-move/vue'
import { createHighlighter } from 'shiki'
import { ref } from 'vue'

import '@shikijs/magic-move/style.css'

const highlighter = await createHighlighter({
  themes: ['nord'],
  langs: ['javascript', 'typescript'],
})

const code = ref(`const hello = 'world'`)

function animate() {
  code.value = `let hi = 'hello'`
}
</script>

<template>
  <ShikiMagicMove
    lang="ts"
    theme="nord"
    :highlighter="highlighter"
    :code="code"
    :options="{ duration: 800, stagger: 0.3, lineNumbers: true }"
  />
  
    Animate
  
</template>

React

Import @shikijs/magic-move/react and pass the highlighter instance to the ShikiMagicMove component.

tsx
import type { HighlighterCore } from 'shiki'
import { ShikiMagicMove } from '@shikijs/magic-move/react'
import { useEffect, useState } from 'react'
import { createHighlighter } from 'shiki'

import '@shikijs/magic-move/style.css'

function App() {
  const [code, setCode] = useState(`const hello = 'world'`)
  const [highlighter, setHighlighter] = useState<HighlighterCore>()

  useEffect(() => {
    async function initializeHighlighter() {
      const h = await createHighlighter({
        themes: ['nord'],
        langs: ['javascript', 'typescript'],
      })
      setHighlighter(h)
    }
    initializeHighlighter()
  }, [])

  function animate() {
    setCode(`let hi = 'hello'`)
  }

  return (
    
      {highlighter && (
        <>
          <ShikiMagicMove
            lang="ts"
            theme="nord"
            highlighter={highlighter}
            code={code}
            options={{ duration: 800, stagger: 0.3, lineNumbers: true }}
          />
          Animate
        </>
      )}
    
  )
}

Solid

Import @shikijs/magic-move/solid and pass the highlighter instance to the ShikiMagicMove component.

tsx
import { ShikiMagicMove } from '@shikijs/magic-move/solid'
import { bundledLanguages, bundledThemes, createHighlighter } from 'shiki'
import { createResource, createSignal, Show } from 'solid-js'

import '@shikijs/magic-move/style.css'

function App() {
  const [code, setCode] = createSignal(`const hello = 'world'`)

  const [highlighter] = createResource(async () => {
    return await createHighlighter({
      themes: Object.keys(bundledThemes),
      langs: Object.keys(bundledLanguages),
    })
  })

  function animate() {
    setCode(`let hi = 'hello'`)
  }

  return (
    
      <Show when={highlighter()}>
        {h => (
          <>
            <ShikiMagicMove
              lang="ts"
              theme="nord"
              highlighter={h()}
              code={code()}
              options={{ duration: 800, stagger: 0.3, lineNumbers: true }}
            />
            Animate
          </>
        )}
      </Show>
    
  )
}

Svelte

Import @shikijs/magic-move/svelte and pass the highlighter instance to the ShikiMagicMove component.

svelte
<script lang="ts">
  import { ShikiMagicMove } from '@shikijs/magic-move/svelte'
  import { createHighlighter } from 'shiki'

  import '@shikijs/magic-move/style.css'

  const highlighter = createHighlighter({
    themes: ['nord'],
    langs: ['javascript', 'typescript'],
  })

  let code = $state(`const hello = 'world'`)

  function animate() {
    code = `let hi = 'hello'`
  }
</script>

{#await highlighter then highlighter}
  <ShikiMagicMove
    lang="ts"
    theme="nord"
    {highlighter}
    {code}
    options={{ duration: 800, stagger: 0.3, lineNumbers: true }}
  />
  Animate
{/await}

`ShikiMagicMovePrecompiled`

ShikiMagicMovePrecompiled is a lighter variant of ShikiMagicMove that doesn't need Shiki at runtime — useful when you want to ship pre-tokenized step data (e.g. produced at build time) and animate between steps in the browser.

vue
<script setup>
import { ShikiMagicMovePrecompiled } from '@shikijs/magic-move/vue'
import { ref } from 'vue'

const step = ref(1)
const compiledSteps = [/* Compiled token steps */]
</script>

<template>
  <ShikiMagicMovePrecompiled
    :steps="compiledSteps"
    :step="step"
  />
  
    Next
  
</template>

To produce the compiled tokens, run this somewhere with access to Shiki (build script, server, etc.) and serialize the result:

ts
import { codeToKeyedTokens, createMagicMoveMachine } from '@shikijs/magic-move/core'
import { createHighlighter } from 'shiki'

const shiki = await createHighlighter({
  themes: ['nord'],
  langs: ['javascript', 'typescript'],
})

const codeSteps = [
  `const hello = 'world'`,
  `let hi = 'hello'`,
]

const machine = createMagicMoveMachine(
  code => codeToKeyedTokens(shiki, code, {
    lang: 'ts',
    theme: 'nord',
  }),
  {
    // options
  },
)

const compiledSteps = codeSteps.map(code => machine.commit(code).current)

// Pass `compiledSteps` to the precompiled component.
// Since these are plain serialisable objects, you can stringify them at build time
// and rehydrate them in the browser without needing Shiki.

How it works

For a deep dive into the algorithm behind the animations, see Anthony's article The Magic In Shiki Magic Move.


Packages Markdown Exit (`docs/packages/markdown-exit.md`)

@shikijs/markdown-exit

markdown-exit plugin for Shiki.

markdown-exit is a modern TypeScript rewrite of markdown-it with native async rendering support — no extra library needed for async highlighting.

Install

::: code-group

sh [npm]
npm i -D @shikijs/markdown-exit
sh [yarn]
yarn add -D @shikijs/markdown-exit
sh [pnpm]
pnpm add -D @shikijs/markdown-exit
sh [bun]
bun add -D @shikijs/markdown-exit
sh [deno]
deno add npm:@shikijs/markdown-exit

:::

Usage

The default export uses Shiki's shorthands for on-demand loading of themes and languages. Since highlighting is async, use md.renderAsync() to render:

ts
import Shiki from '@shikijs/markdown-exit'
import { createMarkdownExit } from 'markdown-exit'

const md = createMarkdownExit()

md.use(Shiki({
  themes: {
    light: 'vitesse-light',
    dark: 'vitesse-dark',
  }
}))

// Use `md.renderAsync` instead of `md.render`
const html = await md.renderAsync('# Title\n```ts\nconsole.log("Hello, World!")\n```')

Transformers

You can pass transformers to the plugin options to customize the highlighted code.

ts
import Shiki from '@shikijs/markdown-exit'
import { transformerNotationDiff } from '@shikijs/transformers'
import { createMarkdownExit } from 'markdown-exit'

const md = createMarkdownExit()

md.use(Shiki({
  themes: {
    light: 'vitesse-light',
    dark: 'vitesse-dark',
  },
  transformers: [
    transformerNotationDiff(),
  ],
}))

const html = await md.renderAsync(md_string)

Custom `codeToHtml`

If you have a custom codeToHtml function (e.g., from a fine-grained shorthand bundle), use fromAsyncCodeToHtml:

ts
import { fromAsyncCodeToHtml } from '@shikijs/markdown-exit'
import { createMarkdownExit } from 'markdown-exit'
import { codeToHtml } from 'shiki' // Or your custom shorthand bundle

const md = createMarkdownExit()

md.use(
  fromAsyncCodeToHtml(
    codeToHtml,
    {
      themes: {
        light: 'vitesse-light',
        dark: 'vitesse-dark',
      }
    }
  )
)

// Use `md.renderAsync` instead of `md.render`
const html = await md.renderAsync('# Title\n```ts\nconsole.log("Hello, World!")\n```')

Fine-grained Bundle

By default, the full bundle of shiki will be imported. If you are using a fine-grained bundle, you can import from @shikijs/markdown-exit/core and pass your own highlighter:

ts
import { fromHighlighter } from '@shikijs/markdown-exit/core'
import { createMarkdownExit } from 'markdown-exit'
import { createHighlighterCore } from 'shiki/core'
import { createOnigurumaEngine } from 'shiki/engine/oniguruma'

const highlighter = await createHighlighterCore({
  themes: [
    import('@shikijs/themes/vitesse-light')
  ],
  langs: [
    import('@shikijs/langs/javascript'),
  ],
  engine: createOnigurumaEngine(() => import('shiki/wasm'))
})

const md = createMarkdownExit()

md.use(fromHighlighter(highlighter, { /* options */ }))

Packages Markdown It (`docs/packages/markdown-it.md`)

@shikijs/markdown-it

markdown-it plugin for Shiki.

Install

::: code-group

sh [npm]
npm i -D @shikijs/markdown-it
sh [yarn]
yarn add -D @shikijs/markdown-it
sh [pnpm]
pnpm add -D @shikijs/markdown-it
sh [bun]
bun add -D @shikijs/markdown-it
sh [deno]
deno add npm:@shikijs/markdown-it

:::

Usage

text
/* Detailed source-code truncated for AI context efficiency. */

Transformers

You can pass transformers to the plugin options to customize the highlighted code.

text
/* Detailed source-code truncated for AI context efficiency. */

Fine-grained Bundle

By default, the full bundle of shiki will be imported. If you are using a fine-grained bundle, you can import from @shikijs/markdown-it/core and pass your own highlighter:

text
/* Detailed source-code truncated for AI context efficiency. */

With Shorthands

Shiki's shorthands provides on-demand loading of themes and languages, but also makes the highlighting process asynchronous. Unfortunately, markdown-it itself does NOT support async highlighting out of the box.

To workaround this, you can use markdown-it-async by Anthony Fu. Where Shiki also provides an integration with it, you can import fromAsyncCodeToHtml from @shikijs/markdown-it/async.

text
/* Detailed source-code truncated for AI context efficiency. */
```ts\nconsole.log("Hello, World!")\n```')

Transformer Caveats

markdown-it defaults to enforcing <pre><code> as the outermost wrappers of code block html. If you use a custom Shiki transformer, this behavior may be undesirable. For example, if the transformer produces

html
<code>
      …
    </code>

the result after markdown-it processing will be

html
<code>
    
      
        <code>
          …
        </code>
      
    
  </code>

Work around this by adding olets/markdown-it-wrapperless-fence-rule to your markdown-it configuration, or by writing your own markdown-it fence rule (see markdown-it#269).


Packages Monaco (`docs/packages/monaco.md`)

Monaco Editor Integration

Shiki uses the same TextMate grammar and themes as VS Code, so it can be used to highlight Monaco Editor. Shiki provides an official integration for Monaco Editor. You can also use modern-monaco that includes built-in Shiki integration.

@shikijs/monaco

Use Shiki to highlight Monaco Editor.

Monaco's built-in highlighter does not use the full TextMate grammar, which in some cases is not accurate enough. This package allows you to use Shiki's syntax highlighting engine to highlight Monaco, with shared grammars and themes from Shiki.

Heavily inspired by monaco-editor-textmate.

Install

::: code-group

sh [npm]
npm i -D @shikijs/monaco
sh [yarn]
yarn add -D @shikijs/monaco
sh [pnpm]
pnpm add -D @shikijs/monaco
sh [bun]
bun add -D @shikijs/monaco
sh [deno]
deno add npm:@shikijs/monaco

:::

Usage

ts
import { shikiToMonaco } from '@shikijs/monaco'
import * as monaco from 'monaco-editor-core'
import { createHighlighter } from 'shiki'

// Create the highlighter, it can be reused
const highlighter = await createHighlighter({
  themes: [
    'vitesse-dark',
    'vitesse-light',
  ],
  langs: [
    'javascript',
    'typescript',
    'vue'
  ],
})

// Register the languageIds first. Only registered languages will be highlighted.
monaco.languages.register({ id: 'vue' })
monaco.languages.register({ id: 'typescript' })
monaco.languages.register({ id: 'javascript' })

// Register the themes from Shiki, and provide syntax highlighting for Monaco. // [!code highlight:2]
shikiToMonaco(highlighter, monaco)

// Create the editor
const editor = monaco.editor.create(document.getElementById('container'), {
  value: 'const a = 1',
  language: 'javascript',
  theme: 'vitesse-dark',
})

// ...As you use the editor normally

modern-monaco

We highly recommend using modern-monaco that includes built-in Shiki integration. It provides a more convenient API for building Monaco Editor.

Install

::: code-group

sh [npm]
npm i -D modern-monaco
sh [yarn]
yarn add -D modern-monaco
sh [pnpm]
pnpm add -D modern-monaco
sh [bun]
bun add -D modern-monaco
sh [deno]
deno add npm:modern-monaco

:::

Or import it from esm.sh CDN in the browser without a build step:

js
import * as monaco from 'https://esm.sh/modern-monaco'

Usage

html
<monaco-editor theme="vitesse-dark"></monaco-editor>
<script src="app.js" type="module"></script>
js
// app.js
import { lazy, Workspace } from 'modern-monaco'

// create a workspace with initial files
const workspace = new Workspace({
  initialFiles: {
    'index.html': `<html>...</html>`,
    'main.js': `console.log('Hello, world!')`,
  },
  entryFile: 'index.html',
})

// initialize the editor lazily
await lazy({ workspace })

// write a file and open it in the editor
workspace.fs.writeFile('util.js', 'export function add(a, b) { return a + b; }')
workspace.openTextDocument('util.js')

More usage please see the modern-monaco repository.


Packages Next (`docs/packages/next.md`)

Next.js

Shiki does not provide an official integration for Next.js, but it is rather straightforward to use Shiki in Next.js applications.

::: info
Using Shiki on Edge Runtime might cause unintended problems, Shiki relies on lazy imports to load languages and themes.

Serverless Runtime is recommended.
:::

React Server Component

Since Server Components are server-only, you can use the bundled highlighter without worrying the bundle size.

tsx
import type { BundledLanguage } from 'shiki'
import { codeToHtml } from 'shiki'

export default function Page() {
  return (
    <main>
      <CodeBlock lang="ts">
        {[
          'console.log("Hello")',
          'console.log("World")',
        ].join('\n')}
      </CodeBlock>
    </main>
  )
}

interface Props {
  children: string
  lang: BundledLanguage
}

async function CodeBlock(props: Props) {
  const out = await codeToHtml(props.children, {
    lang: props.lang,
    theme: 'github-dark'
  })

  return 
}

Custom Components

You can also call codeToHast to get the HTML abstract syntax tree, and render it using hast-util-to-jsx-runtime. With this method, you can render your own pre and code components.

tsx
import type { JSX } from 'react'
import type { BundledLanguage } from 'shiki'
import { toJsxRuntime } from 'hast-util-to-jsx-runtime'
import { Fragment } from 'react'
import { jsx, jsxs } from 'react/jsx-runtime'
import { codeToHast } from 'shiki'

export default function Page() {
  return (
    <main>
      <CodeBlock lang="ts">
        {[
          'console.log("Hello")',
          'console.log("World")',
        ].join('\n')}
      </CodeBlock>
    </main>
  )
}

interface Props {
  children: string
  lang: BundledLanguage
}

async function CodeBlock(props: Props) {
  const out = await codeToHast(props.children, {
    lang: props.lang,
    theme: 'github-dark'
  })

  return toJsxRuntime(out, {
    Fragment,
    jsx,
    jsxs,
    components: {
      // your custom `pre` element
      pre: props => 
    },
  }) as JSX.Element
}

React Client Component

For client components, they are pre-rendered on server and hydrated/rendered on client.
We can start by creating a client CodeBlock component.

Create a shared.ts for highlighter:

ts
import type { JSX } from 'react'
import type { BundledLanguage } from 'shiki/bundle/web'
import { toJsxRuntime } from 'hast-util-to-jsx-runtime'
import { Fragment } from 'react'
import { jsx, jsxs } from 'react/jsx-runtime'
import { codeToHast } from 'shiki/bundle/web'

export async function highlight(code: string, lang: BundledLanguage) {
  const out = await codeToHast(code, {
    lang,
    theme: 'github-dark'
  })

  return toJsxRuntime(out, {
    Fragment,
    jsx,
    jsxs,
  }) as JSX.Element
}

In your codeblock.tsx:

tsx
'use client'
import { JSX, useLayoutEffect, useState } from 'react'
import { highlight } from './shared'

export function CodeBlock({ initial }: { initial?: JSX.Element }) {
  const [nodes, setNodes] = useState(initial)

  useLayoutEffect(() => {
    void highlight('console.log("Rendered on client")', 'ts').then(setNodes)
  }, [])

  return nodes ?? Loading...
}

The initial prop can be passed from a server component to pre-render the code block on server.

In your page.tsx:

tsx
import { CodeBlock } from './codeblock'
import { highlight } from './shared'

export default async function Page() {
  // `initial` is optional.
  return (
    <main>
      <CodeBlock initial={await highlight('console.log("Rendered on server")', 'ts')} />
    </main>
  )
}

::: info
The above example uses the shiki/bundle/web bundle. You can change it to Fine-grained Bundle to fully control the bundled languages/themes.
:::

Performance

Shiki lazy loads the requested languages and themes, the Next.js bundler can handle lazy imports automatically.
Importing shiki or its web bundle is efficient enough for most Next.js applications, Fine-grained Bundle won't significantly impact the bundle size.

In addition, you can use the createHighlighter API to preload specific languages and themes.
Please refer to Highlighter Usage for further details.

Highlighter Instance

If you define a highlighter (without await) as a global variable, you can reference it directly from server and client components.

ts
import { createHighlighter } from 'shiki'

const highlighter = createHighlighter({
  themes: ['nord'],
  langs: ['javascript'],
})

// Inside an async server component, or client side `useEffect`
const html = (await highlighter).codeToHtml('const a = 1', {
  lang: 'javascript',
  theme: 'nord'
})

Packages Nuxt (`docs/packages/nuxt.md`)

Nuxt

Nuxt is a general-purpose meta-framework that gives you full control over your application. You can install Shiki along with integrations like (markdown-it, rehype) or import manually as you needed.

On top of that, Nuxt also provide some module integrations for Shiki allowing you use it easier:

TwoSlash Integration

  • nuxt-content-twoslash - A Nuxt module that adds Twoslash support to Nuxt Content. Twoslash information are evaluated at build time.

Packages Rehype (`docs/packages/rehype.md`)


outline: deep

@shikijs/rehype

rehype plugin for Shiki.

Install

::: code-group

sh [npm]
npm i -D @shikijs/rehype
sh [yarn]
yarn add -D @shikijs/rehype
sh [pnpm]
pnpm add -D @shikijs/rehype
sh [bun]
bun add -D @shikijs/rehype
sh [deno]
deno add npm:@shikijs/rehype

:::

Usage

text
/* Detailed source-code truncated for AI context efficiency. */

The default export of @shikijs/rehype uses a shared instance of shiki from getSingletonHighlighter, which will persist across processes. If you want full control over the highlighter lifecycle, use Fine-grained Bundle @shikijs/rehype/core instead.

Fine-grained Bundle

By default, the full bundle of shiki will be imported. If you are using a fine-grained bundle, you can import rehypeShikiFromHighlighter from @shikijs/rehype/core and pass your own highlighter:

text
/* Detailed source-code truncated for AI context efficiency. */

Features

Inline Code

You can also highlight inline codes with the inline option.

Option Example Description
false - Disable inline code highlighting (default)
'tailing-curly-colon' let a = 1{:js} Highlight with a {:language} marker inside the code block

Enable inline on the Rehype plugin:

text
/* Detailed source-code truncated for AI context efficiency. */

Then you can use inline code in markdown:

md
This code `console.log("Hello World"){:js}` will be highlighted.

Postprocess Transformers

::: info
The postprocess transformer hook is only invoked when producing HTML strings (i.e., when using codeToHtml). Since @shikijs/rehype operates on HAST (Hypertext Abstract Syntax Tree) rather than HTML strings, postprocess transformers will not be executed.
:::

This design is intentional: running postprocess hooks in rehype would require converting HAST → HTML → running postprocess → parsing back to HAST, which changes the semantics and could surprise users expecting HAST-only transformers.

Workaround for HTML-based Postprocessing

If you need to run HTML-based postprocessing with rehype, you can apply a root transformer that:

  1. Converts the HAST fragment to HTML using hast-util-to-html
  2. Runs your HTML transformers
  3. Converts back to HAST using hast-util-from-html

Example:

ts
import { fromHtml } from 'hast-util-from-html'
import { toHtml } from 'hast-util-to-html'

const file = await unified()
  .use(remarkParse)
  .use(remarkRehype)
  .use(rehypeShiki, {
    themes: {
      light: 'vitesse-light',
      dark: 'vitesse-dark',
    },
    transformers: [
      {
        name: 'custom-html-postprocessor',
        root(node) {
          // Convert HAST to HTML
          const html = toHtml(node)

          // Apply your HTML transformations
          const processedHtml = myCustomPostprocess(html)

          // Parse back to HAST
          const newNode = fromHtml(processedHtml, { fragment: true })

          // Replace the node
          return newNode
        }
      }
    ]
  })
  .use(rehypeStringify)
  .process(await fs.readFile('./input.md'))

Packages Stream (`docs/packages/stream.md`)


outline: deep

@shikijs/stream

Streaming colorization for Shiki. Useful for highlighting text streams like LLM outputs, where code arrives incrementally and you want each chunk highlighted as it lands without re-tokenizing the whole document.

Install

::: code-group

sh [npm]
npm i -D @shikijs/stream
sh [yarn]
yarn add -D @shikijs/stream
sh [pnpm]
pnpm add -D @shikijs/stream
sh [bun]
bun add -D @shikijs/stream
sh [deno]
deno add npm:@shikijs/stream

:::

Usage

Create a transform stream with CodeToTokenTransformStream and .pipeThrough your text stream:

ts
import { CodeToTokenTransformStream } from '@shikijs/stream'
import { createHighlighter, createJavaScriptRegexEngine } from 'shiki'

// Initialize the Shiki highlighter somewhere in your app
const highlighter = await createHighlighter({
  langs: [/* ... */],
  themes: [/* ... */],
  engine: createJavaScriptRegexEngine(),
})

// The ReadableStream<string> you want to highlight
const textStream = getTextStreamFromSomewhere()

// Pipe the text stream through the token stream
const tokensStream = textStream
  .pipeThrough(new CodeToTokenTransformStream({
    highlighter,
    lang: 'javascript',
    theme: 'nord',
    allowRecalls: true, // see explanation below
  }))

`allowRecalls`

Due to the fact that highlighting may change based on the context of the code, themed tokens can be re-evaluated as the stream progresses. Because streams are one-directional, @shikijs/stream emits a special recall token that tells the receiver to discard the last N tokens that changed.

By default, CodeToTokenTransformStream only emits stable tokens — no recalls. The trade-off is coarser granularity, usually one line at a time.

For stream consumers that can handle recalls (e.g. the bundled Vue, React, Solid, and Svelte components), set allowRecalls: true to get finer-grained tokens.

Typically recalls should be handled like:

ts
import type { RecallToken } from '@shikijs/stream'
import type { ThemedToken } from 'shiki'

const receivedTokens: ThemedToken[] = []

tokensStream.pipeTo(new WritableStream({
  async write(token) {
    if ('recall' in token) {
      // discard the last `token.recall` tokens
      receivedTokens.length -= token.recall
    }
    else {
      receivedTokens.push(token)
    }
  },
}))

Consume the Token Stream

Manually

ts
tokensStream.pipeTo(new WritableStream({
  async write(token) {
    console.log(token)
  },
}))

Or in Node.js:

ts
for await (const token of tokensStream) {
  console.log(token)
}

Vue

vue
<script setup lang="ts">
import { ShikiStreamRenderer } from '@shikijs/stream/vue'

// get the token stream
</script>

<template>
  <ShikiStreamRenderer :stream="tokensStream" />
</template>

React

tsx
import { ShikiStreamRenderer } from '@shikijs/stream/react'

export function MyComponent() {
  // get the token stream
  return <ShikiStreamRenderer stream={tokensStream} />
}

Solid

tsx
import { ShikiStreamRenderer } from '@shikijs/stream/solid'

export function MyComponent() {
  // get the token stream
  return <ShikiStreamRenderer stream={tokensStream} />
}

Svelte

svelte
<script lang="ts">
import { ShikiStreamRenderer } from '@shikijs/stream/svelte'

// get the token stream
</script>

<ShikiStreamRenderer stream={tokensStream} />

Cached Renderer

@shikijs/stream also ships a simplified renderer for the common case of an incrementally-updated code string (rather than a token stream).

::: warning Experimental
This API is experimental and may change.
:::

Vue

vue
<script setup lang="ts">
import { ShikiCachedRenderer } from '@shikijs/stream/vue'
import { createHighlighter, createJavaScriptRegexEngine } from 'shiki'
import { onMounted, ref } from 'vue'

const highlighter = await createHighlighter({
  langs: [/* ... */],
  themes: [/* ... */],
  engine: createJavaScriptRegexEngine(),
})

const code = ref('') // code should only be updated incrementally

// for demo purposes
onMounted(() => {
  setInterval(() => {
    code.value += '\nconsole.log("Hello, world!");'
  }, 1000)
})
</script>

<template>
  <ShikiCachedRenderer
    :highlighter="highlighter"
    :code="code"
    lang="js"
    theme="vitesse-light"
  />
</template>

Packages Transformers (`docs/packages/transformers.md`)


outline: deep

@shikijs/transformers

Common transformers for Shiki, inspired by shiki-processor.

Install

::: code-group

sh [npm]
npm i -D @shikijs/transformers
sh [yarn]
yarn add -D @shikijs/transformers
sh [pnpm]
pnpm add -D @shikijs/transformers
sh [bun]
bun add -D @shikijs/transformers
sh [deno]
deno add npm:@shikijs/transformers

:::

Usage

text
/* Detailed source-code truncated for AI context efficiency. */

Unstyled

Transformers only applies classes and does not come with styles; you can provide your own CSS rules to style them properly.

Matching Algorithm

We found that the algorithm for matching comments in v1 is sometime conterintuitive, where we are trying to fix it in a progressive way. Since v1.29.0, we introduced a new matchAlgorithm option to most of the transformer for you to toggle between different matching algorithms. Right now, the default is v1 which is the old algorithm, and v3 is the new algorithm. When Shiki v3 is landed, the default will be v3.

ts
const html = await codeToHtml(code, {
  lang: 'ts',
  theme: 'nord',
  transformers: [
    transformerNotationDiff({
      matchAlgorithm: 'v3', // [!code hl]
    }),
  ],
})

`matchAlgorithm: 'v1'`

The matching algorithm mostly affects the single-line comment matching, in v1, it will count the comment line as the first line, while in v3, it will count start from the comment line:

ts
// [\!code highlight:3]
console.log('highlighted') // [!code hl]
console.log('highlighted') // [!code hl]
console.log('not highlighted')

`matchAlgorithm: 'v3'`

In v3, the matching algorithm will start counting from the line below the comment:

ts
// [\!code highlight:2]
console.log('highlighted') // [!code hl]
console.log('highlighted') // [!code hl]
console.log('not highlighted')

Transformers

`transformerNotationDiff`

Use [!code ++] and [!code --] to mark added and removed lines.

md
```ts
console.log('hewwo') // [\!code --]
console.log('hello') // [\!code ++]
console.log('goodbye')
```

Renders (with custom CSS rules):

ts
console.log('hewwo') // [!code --]
console.log('hello') // [!code ++]
console.log('goodbye')
  • // [!code ++] outputs: <span class="line diff add">
  • // [!code --] outputs: <span class="line diff remove">
  • The outer <pre> tag is modified: <pre class="has-diff">

::: details HTML Output

html
<code>
    
    function(){
      
      console.log(&#39;hewwo&#39;) 
    
      
      console.log(&#39;hello&#39;) 
    
    }
    
  </code>

:::


`transformerNotationHighlight`

Use [!code highlight] to highlight a line.

md
```ts
console.log('Not highlighted')
console.log('Highlighted') // [\!code highlight]
console.log('Not highlighted')
```

Renders (with custom CSS rules):

ts
console.log('Not highlighted')
console.log('Highlighted') // [!code highlight]
console.log('Not highlighted')
  • // [!code highlight] outputs: <span class="line highlighted">
  • The outer <pre> tag is modified: <pre class="has-highlighted">

You can also highlight multiple lines with a single comment:

md
```ts
// [\!code highlight:3]
console.log('Highlighted')
console.log('Highlighted')
console.log('Highlighted')
console.log('Not highlighted')
```

```ts
console.log('Not highlighted')
// [\!code highlight:1]
console.log('Highlighted')
console.log('Not highlighted')
```

Renders:

ts
// [!code highlight:3]
console.log('Highlighted')
console.log('Highlighted')
console.log('Highlighted')
console.log('Not highlighted')
ts
console.log('Not highlighted')
// [!code highlight:1]
console.log('Highlighted')
console.log('Not highlighted')

`transformerNotationWordHighlight`

Use [!code word:Hello] to highlight the word Hello in any subsequent code.

md
```ts
// [\!code word:Hello]
const message = 'Hello World'
console.log(message) // prints Hello World
```

Renders (with custom CSS rules):

ts
// [!code word:Hello]
const message = 'Hello World'
console.log(message) // prints Hello World

Outputs: <span class="highlighted-word">Hello</span> for matched words.

You can also specify the number of lines to highlight words on, e.g. [!code word:Hello:1] will only highlight occurrences of Hello on the next line.

md
```ts
// [\!code word:Hello:1]
const message = 'Hello World'
console.log(message) // prints Hello World
```

Renders:

ts
// [!code word:Hello:1]
const message = 'Hello World'
console.log(message) // prints Hello World

`transformerNotationFocus`

Use [!code focus] to focus a line.

md
```ts
console.log('Not focused');
console.log('Focused') // [\!code focus]
console.log('Not focused');
```

Renders (with custom CSS rules):

ts
console.log('Not focused')
console.log('Focused') // [!code focus]
console.log('Not focused')
  • Outputs: <span class="line focused">
  • The outer <pre> tag is modified: <pre class="has-focused">

You can also focus multiple lines with a single comment:

md
```ts
// [\!code focus:3]
console.log('Focused')
console.log('Focused')
console.log('Focused')
console.log('Not focused')
```

Renders:

ts
// [!code focus:3]
console.log('Focused')
console.log('Focused')
console.log('Focused')
console.log('Not focused')

`transformerNotationErrorLevel`

Use [!code error], [!code warning], and [!code info] to mark a line with an error, warning, or info levels.

md
```ts
console.log('No errors or warnings')
console.error('Error') // [\!code error]
console.warn('Warning') // [\!code warning]
console.log('Info') // [\!code info]
```
  • Outputs: <span class="line highlighted error"> for errors
  • Outputs: <span class="line highlighted warning"> for warnings
  • Outputs: <span class="line highlighted info"> for info
  • The outer <pre> tag is modified: <pre class="has-highlighted">

With some additional CSS rules, you can make it look like this:

ts
console.log('No errors or warnings')
console.error('Error') // [!code error]
console.warn('Warning') // [!code warning]
console.log('Info') // [!code info]

`transformerRenderWhitespace`

Render whitespaces (tabs and spaces) as individual spans, with classes tab and space.

Options:

  • position: 'all' | 'boundary' | 'trailing' | 'leading'. Default 'all'.

With some additional CSS rules, you can make it look like this:

js
function block( ) {
  space( )
		tab( ) 
}

::: details Example CSS

css
pre.shiki .tab,
pre.shiki .space {
  position: relative;
}

pre.shiki .tab::before {
  content: '⇥';
  position: absolute;
  opacity: 0.3;
}

pre.shiki .space::before {
  content: '·';
  position: absolute;
  opacity: 0.3;
}

:::


`transformerRenderIndentGuides`

Render indentations as individual spans, with class indent.

With some additional CSS rules, you can make it look like this:

js
function func() {
  console.log(1);

  for (const i of []) {
    console.log(2);
  }
}

::: details Example CSS

css
pre.shiki .indent {
  display: inline-block;
  position: relative;
  left: var(--indent-offset);
}

pre.shiki .indent:empty {
  height: 1lh;
  vertical-align: bottom;
}

pre.shiki .indent::before {
  content: '';
  position: absolute;
  opacity: 0.15;
  width: 1px;
  height: 100%;
  background-color: currentColor;
}

:::


`transformerRenderLineNumber`

Render line numbers as individual spans, with class line-number.

ts
import { transformerRenderLineNumber } from '@shikijs/transformers'

const html = await codeToHtml(code, {
  transformers: [
    transformerRenderLineNumber(),
  ],
})

Options:

  • start: Start number. Default 1.
  • classLineNumber: Class for line number. Default 'line-number'.

With some additional CSS rules, you can make it look like this:

css
pre.shiki .line-number {
  user-select: none;
  opacity: 0.5;
  margin-right: 1em;
}

`transformerMetaHighlight`

Highlight lines based on the meta string provided on the code snippet.

md
```js {1,3-4}
console.log('1')
console.log('2')
console.log('3')
console.log('4')
```

Renders (with custom CSS rules):

js {1,3-4}
console.log('1')
console.log('2')
console.log('3')
console.log('4')
  • Outputs: <span class="line highlighted"> for included lines.

`transformerMetaWordHighlight`

Highlight words based on the meta string provided on the code snippet.

md
```js /Hello/
const msg = 'Hello World'
console.log(msg)
console.log(msg) // prints Hello World
```

Renders (with custom CSS rules):

js /hello/
const msg = 'Hello World'
console.log(msg) // prints Hello World

Outputs: <span class="highlighted-word">Hello</span> for matched words.


`transformerCompactLineOptions`

Support for shiki's lineOptions that is removed in shiki.


`transformerRemoveLineBreak`

Remove line breaks between <span class="line">. Useful when you set display: block to .line in CSS.


`transformerRemoveNotationEscape`

Transform // [\!code ...] to // [!code ...].
Avoid rendering the escaped notation syntax as it is.


`transformerStyleToClass`

Convert Shiki's inline styles to unique classes.

Class names are generated based on the hash value of the style object with the prefix/suffix you provide. You can put this transformer in multiple highlights passes and then get the CSS at the end to reuse the exact same styles. As Shiki doesn't handle CSS, it's on your integration to decide how to extract and apply/bundle the CSS.

For example:

ts
import { transformerStyleToClass } from '@shikijs/transformers'
import { codeToHtml } from 'shiki'

const toClass = transformerStyleToClass({ // [!code highlight:3]
  classPrefix: '__shiki_',
})

const code = `console.log('hello')`
const html = await codeToHtml(code, {
  lang: 'ts',
  themes: {
    dark: 'vitesse-dark',
    light: 'vitesse-light',
  },
  defaultColor: false,
  transformers: [toClass], // [!code highlight]
})

// The transformer instance exposes some methods to get the CSS
const css = toClass.getCSS() // [!code highlight]

// use `html` and `css` in your app

HTML output:

html
<code>
  console
  .
  log
  (
  '
  hello
  '
  )
</code>

CSS output:

css
.__shiki_14cn0u {
  --shiki-dark: #bd976a;
  --shiki-light: #b07d48;
}
.__shiki_ps5uht {
  --shiki-dark: #666666;
  --shiki-light: #999999;
}
.__shiki_1zrdwt {
  --shiki-dark: #80a665;
  --shiki-light: #59873a;
}
.__shiki_236mh3 {
  --shiki-dark: #c98a7d77;
  --shiki-light: #b5695977;
}
.__shiki_1g4r39 {
  --shiki-dark: #c98a7d;
  --shiki-light: #b56959;
}
.__shiki_9knfln {
  --shiki-dark: #dbd7caee;
  --shiki-light: #393a34;
  --shiki-dark-bg: #121212;
  --shiki-light-bg: #ffffff;
}

`transformerRemoveComments`

Remove comments from the code. It works by checking the internal grammar token metadata to determine if the token is a comment.

This transformer requires includeExplanation: true to work.

ts
import { transformerRemoveComments } from '@shikijs/transformers'

const html = await codeToHtml(code, {
  lang: 'ts',
  includeExplanation: true, // [!code highlight]
  transformers: [
    transformerRemoveComments(), // [!code highlight]
  ],
})

Options:

  • removeEmptyLines: Remove lines that become empty after removing comments. Default true.

For example:

md
```js
// This is a comment
const x = 1 // Inline comment
/* Block comment */
const y = 2

// Another comment
```

Will renders:

js
const x = 1
const y = 2

Packages Twoslash (`docs/packages/twoslash.md`)


outline: deep

@shikijs/twoslash

A Shiki transformer for Twoslash, provide inline type hover inside code blocks.

TwoSlash Notation Reference.

Install

::: code-group

sh [npm]
npm install -D @shikijs/twoslash
sh [yarn]
yarn add -D @shikijs/twoslash
sh [pnpm]
pnpm add -D @shikijs/twoslash
sh [bun]
bun add -D @shikijs/twoslash
sh [deno]
deno add npm:@shikijs/twoslash

:::

This package is a transformer addon to Shiki. This means that for every integration that supports passing Shiki transformers, you can use this package.

text
/* Detailed source-code truncated for AI context efficiency. */

The default output is unstyled. You need to add some extra CSS to make them look good.

If you want to run Twoslash on browsers or workers, reference to the CDN Usage section.

Renderers

Thanks to the flexibility of hast, this transformer allows customizing how each piece of information is rendered in the output HTML with ASTs.

We provide two renderers built-in, and you can also create your own:


`rendererRich`

Source code

::: tip
This is the default renderer since v0.10.0.
:::

This renderer provides a more explicit class name is prefixed with twoslash- for better scoping.
In addition, it runs syntax highlighting on the hover information.

ts twoslash
// @twoslash-cache: {"v":1,"hash":"9bafe2d835b3a768cc39f5a28647084c02c621d41bbebd6324dead19238c244e","data":"N4Igdg9gJgpgziAXAbVAFwJ4AcZJACwgDcYAnEAGhDRgA808AKAQwBsBLZuASgAIAzAK5gAxmnYQwvUjDCwZpAErsR+RhCzjJcAPyJei2fLLLVAeU0SwPfQBUA7hDisu+Q3LJkAOmHYBbLAhSNGkjTyUVfEoQKAgRBEQQAEEpNhpSMGZxElCPBV40fCzeLFJidnk4XgAjGDR0kpl+dloYKF4RFzgqzL94Ch97dkLeOAwwNGZaXnx2AHN8DgWQ/iCC/BhedjBVgro0ADpouEngpABOKlZZOcKkAEYAJipTubq8GTyTSOiOMFxEAAGKiqZikZhiMgXAC+FHQ2ABBGIUJe+yYbE4PAEwjEVgK4Osq1IfVIDicXTUGi01j0vFsBLgRJJZOcrgAkh5aBZqTZeABlWYAa3Y9OYhKCJJ8/kCwXxYsZErILIp0Vi8TwADEIWgghhsaJqQUIB0ZFlNsx+UL2HLxcSyAI1mhHKy4PgthMYHNwTyjlQTmCGIhHgBmK43O6Ie6Xahgt6B6gMplK50qq7bAHAkCg8GQ8hB4Ow+E4PCEEjkVH0dEcLh8NCJxWklOudSWbS00W25lN10c2Bc1vWbj6AXsYUdhV20hSgJBEJ1+VJxvk1yquIJEBasS6/W4yRGk0wM28C0j4U2ickh2kArdt3bGherJWOC+kD+s6IADs9zDYFuUUjaNXneRJ507ZNl1dX50yQZ4syKHN0iQQFC2oBES2RctqDRRJGFKDQyEwPhx0XZVXG5Z8Dk+YxSHbW93Bo3gAB9eGEWBmn+KBV3VRIAGFBBOCA/FyGiqh1XhYBECpNkIexeEPVR3V2V0IEEVh2lqETPC4v1TkDc4AFZf3/JAAA4XljECQGo8JoP+JBQ3gsFtShINUMwYtElLFFsMrXCMRrLSFFMSkB10fQGPCEKKO0Ic6XosIFGnGUQhs4KfioNV1xSY9WHSTJsk2NL7UKYp8KIaTSCqWp6ntUoYGaVp2k6LgemYPo4AGMAhhGMYJimGZ5kWIaVkdDYlONGh6Ffd9A3uQEjJAa4/wjJ4LNIOMPkS75VDsgFHOzFy8yeaEAF0QWgRFpVnXhgCCnb8Aoc9SNvXhoQEMphIAcgAAVdUd2AAKzgAB6J1IPwL6fB8MCLwgl01GAHxeHu0h9GKiJVEYPgQZB3gAB4AFpCZ8aFuGiPpJiQUB9lkOArDwNAEGhaEgA"}
import { rendererRich, transformerTwoslash } from '@shikijs/twoslash'

transformerTwoslash({
  renderer: rendererRich() // <--
})

Here are a few examples with the built-in style-rich.css:

text
/* Detailed source-code truncated for AI context efficiency. */
text
/* Detailed source-code truncated for AI context efficiency. */

`rendererClassic`

Source code

This renderer aligns with the output of legacy shiki-twoslash.

ts twoslash
// @twoslash-cache: {"v":1,"hash":"be50298f5ea6976d501abea603d4b03abe8280f3bf0a7b10df97e47172449e7c","data":"N4Igdg9gJgpgziAXAbVAFwJ4AcZJACwgDcYAnEAGhDRgA808AKAQwBsBLZuASgAIAzAK5gAxmnYQwvUjDCwZpAMKsucdiMbdEvACoB3CHBVx8AJVnyyAHTDsAtlgik00i2TLLV6yiCgQRCIggOvgwvLD8zIKsLjJy7qS8bOwA5rZgKbx67Gj4vLlhTqnsYGy8AAYAAibsANbsAFZwAPRoBkZc+OW8EIJoWH0AdD5waMzOSACcVKyyKblIAIwArFRjpCkwDEFxlkrGaiI+HGC4iAAMVCL448xiZFMAvhTo2GcExA9rdNsgLBxcPhCUTiST5UjMMBwfhOOxkfSGYz4RgQLCgqEAfm0OghUJhpDhpARHRMAEl4rQAPJoiRQrS8ADK+Dq7BxkOhsOstgcThcaFxHIJ8PaSJ8fgCeAAYnc0E4MAJhGJafkILwRDJmDQkozmfVwez8YSBE58iLOrwSjQUhD0XBhlRRuNtgAmABsMzmC0Qi2m1HGm1+/INnKJZpMxxKZ0uIGut3u5EQbuerxweEIJHI33oTGSgP1eJDxKRKJpkjgWN0AsNwsRnXJsCppbp2iZLLZBaFpBs9kcznzgsJRc6Yv8gRA0rEcoVIOVsrVGq1zB1LP71cS+NNtZMFrAVpttLtI3W2wA7AAWD0ZL0+tb+rZ4IMdwdh/AR05IZ1XG4Q+NIc7J6g3jTT5M2oH4mCwUhUTITA+HbAcaxJfBqVtQZdgSCshxMcx4gUXgAB9eGECJIygEcJSCRRBFGCA7FcXCyDgFVwhgER2FgXhCD0XgYDuPIShhXgTF6VgoF4AAjMJ0JkMiHWPKYAA5L3mV9ECUv0NnvHY3AUN8zgAZi/OMaATZ0AMwVMgnTL4wOzIJ/k4Hh6L2Tw4EOTRsRfHC9m7Hk+2kjwDm8KhxTHEIwgiKIYmchIkg4NISkybJcnyUIelIYpSlYCpql1RoWjaLcuh6PoBjQe0QEdCZvXOdTZivVSVlvTTfgC/YvCOGZIyQQyY2/GUHm9MyAF0rmgd4e15XhgBihRXMOChV0LF9eEeAQoLogByXKWSaVoX02mwbEfBDQyKxhgBsXhZrIbQ2vm9RNF4Zpml4AAeABaD6bEebgfDhMYkFAH5ZDUSQHwQR5HiAA="}
import { rendererClassic, transformerTwoslash } from '@shikijs/twoslash'

transformerTwoslash({
  renderer: rendererClassic() // <--
})

You might need to reference shiki-twoslash's CSS to make it look good. Here we also copied the CSS from shiki-twoslash but it might need some cleanup.

`rendererFloatingVue`

Source code

This renderer outputs Vue template syntax that using Floating Vue as the popup component (to render it outside the containers). This renderer is NOT directly usable but an internal renderer for the VitePress integration. Listing it here for reference if you want to create your own renderer.

Options

Explicit Trigger

When integrating with @shikijs/markdown-it or rehype-shiki, we may not want Twoslash to run on every code block. In this case, we can set explicitTrigger to true to only run on code blocks with twoslash presented in the codeframe.

ts twoslash
// @twoslash-cache: {"v":1,"hash":"c33fc7b243f56e1595f93ca6e9e052fed95abb58a9edc5376b448c62984626f1","data":"N4Igdg9gJgpgziAXAbVAFwJ4AcZJACwgDcYAnEAGhDRgA808AKAQwBsBLZuASgAIAzAK5gAxmnYQwvNKWZg4/CKQC2ZACoB3CHFZd8jCFnGS4AfkS81s+YpXqtOvQEkwsWgHkjE+dwsBlfHYAa3YrOQUlVVIAHTB2ZSwlNGlrCLtSTW1dOHxKECgIEQREEAAxZjElDAFhMW9pCF4RUhhmGl5mXgDg9hTw2yiBJWkHbPxedjAaAHNZY3kAOjy4NGZSBkQATipWGDBptFzEAEZt6jXpmA3qVIH7LL08jjBcRAAGKhF8NYqaci2AL4UdDYV4EYhkPI0ehMNicHh9GyRe6OHIGLwmcyWW7IjKjZyuOieeY8fyBEJhJHpWLxRLrRFpKKZVG5KgFIp4cqVUjVISieYNJotNowDpdcm9GT9XFDUgjB45CZTGCzNreOBLKgrNYbACsAHYdnsDkdTlRVqRLtcpVSmfick9Jq8AEyfb6yMSQ95AkE4PCEEjkc10a6MLCkQxkTB8SmMlFjYnqhZ0LAcETsNBWdjTS6kLEAIwgEF2cl4AB9eAAlFUAUVoWHLvGEsH4TqgeXZxRA1YAjoJ2C04LwAAZoe34YeC/Oi8PwPY0KBK6T4UUiaCi/OsQpBXiqVaC5hYVPVQ7sIc2uNB87TYrIZD5GD8ZiCVgMKhP1hwXAAXW/WotGwABzHEa+yHEgxy6uaFxXHgKZphmWY5pCOxOkgADMbo/J6/zOgCf4gGusB4LSSS8MADJ3HiCrjACAgRsovAAOQAAI5D0ABWcAAPRjjRTGxLEF5UcyYyMMAsS8Lw8HsOmmakNmuYWDIgiitx3G8AAPAAtNpsQAtweR7swSCgCGexwN4eBoAgAIAkAA="}
import { transformerTwoslash } from '@shikijs/twoslash'

transformerTwoslash({
  explicitTrigger: true // <--
})
md
In markdown, you can use the following syntax to trigger Twoslash:

```ts
// this is a normal code block
```

```ts twoslash
// this will run Twoslash
```

Integrations

While you can set up Twoslash with Shiki on your own with the instructions above, you can also find high-level integrations with frameworks and tools here:

  • VitePress - A plugin to enable Twoslash support in VitePress.
  • Nuxt - A module to enable Twoslash for Nuxt Content.
  • Vocs - Vocs has TwoSlash support built-in.
  • Slidev - Slidev has TwoSlash support built-in.

Recipes

CDN Usage

By default twoslash runs on Node.js and relies on your local system to resolve TypeScript and types for the imports. Import it directly in non-Node.js environments would not work.

Luckily, Twoslash implemented a virtual file system, which allow you to provide your own files for TypeScript to resolve in memory. However, loading these files in the browser is still a challenge. Thanks to the work on the TypeScript WebSite, the TypeScript team has provided some utilities to fetch types on demand through CDN, they call it Automatic Type Acquisition (ATA).

We make tiny wrappers around the building blocks and provide an easy-to-use API in twoslash-cdn. For example:

js
// FIXME: Replace with explicit versions in production
import { createTransformerFactory, rendererRich } from 'https://esm.sh/@shikijs/twoslash@latest/core'
import { codeToHtml } from 'https://esm.sh/shiki@latest'
import { createTwoslashFromCDN } from 'https://esm.sh/twoslash-cdn@latest'
import { createStorage } from 'https://esm.sh/unstorage@latest'
import indexedDbDriver from 'https://esm.sh/unstorage@latest/drivers/indexedb'

// ============= Initialization =============

// An example using unstorage with IndexedDB to cache the virtual file system
const storage = createStorage({
  driver: indexedDbDriver({ base: 'twoslash-cdn' }),
})

const twoslash = createTwoslashFromCDN({
  storage,
  compilerOptions: {
    lib: ['esnext', 'dom'],
  },
})

const transformerTwoslash = createTransformerFactory(twoslash.runSync)({
  renderer: rendererRich(),
})

// ============= Execution =============

const app = document.getElementById('app')

const source = `
import { ref } from 'vue'

console.log("Hi! Shiki + Twoslash on CDN :)")

const count = ref(0)
//     ^?
`.trim()

// Before rendering, we need to prepare the types, so that the rendering can happen synchronously
await twoslash.prepareTypes(source)

// Then we can render the code
app.innerHTML = await codeToHtml(source, {
  lang: 'ts',
  theme: 'vitesse-dark',
  transformers: [transformerTwoslash],
})

Packages Vitepress (`docs/packages/vitepress.md`)


outline: deep

VitePress Integration

VitePress uses Shiki under the hood, so you don't need explicit integration.

VitePress provides a few options for customizing Shiki. Learn more about them in the VitePress documentation.

Twoslash

To enable TypeScript Twoslash (type hover on code snippets) in VitePress, we provide a VitePress plugin for easy setup. Pre-styled, with Floating Vue to display the type information out side of the code container.

Setup

::: code-group

sh [npm]
npm i -D @shikijs/vitepress-twoslash
sh [yarn]
yarn add -D @shikijs/vitepress-twoslash
sh [pnpm]
pnpm add -D @shikijs/vitepress-twoslash
sh [bun]
bun add -D @shikijs/vitepress-twoslash
sh [deno]
deno add npm:@shikijs/vitepress-twoslash

:::

In your .vitepress/config.ts:

ts [.vitepress/config.ts]
import { transformerTwoslash } from '@shikijs/vitepress-twoslash' // [!code hl]
import { defineConfig } from 'vitepress'

export default defineConfig({
  markdown: {
    codeTransformers: [
      transformerTwoslash() // [!code hl]
    ],
    // [!code hl:2]
    // Explicitly load these languages for types highlighting
    languages: ['js', 'jsx', 'ts', 'tsx']
  }
})

And then in your .vitepress/theme/index.ts, install the Vue plugin and import the css with @shikijs/vitepress-twoslash/styles.css.

text
/* Detailed source-code truncated for AI context efficiency. */

::: details About style.css

For easier setup, @shikijs/vitepress-twoslash/styles.css bundles the styles from floating-vue and @shikijs/twoslash/style-rich.css so you only need a single entry. If you are using a custom floating-vue style or want to have more control of the styles, you can expand them as:

ts
import '@shikijs/vitepress-twoslash/style.css'

// Equivalent to:
import '@shikijs/twoslash/style-rich.css'
import 'floating-vue/dist/style.css'
import '@shikijs/vitepress-twoslash/style-core.css'

:::

That's it, you can now use ts twoslash in your markdown files to enable the beautiful type hover.

md
```ts twoslash
console.log('hello')
//      ^?
```

It will be rendered as:

text
/* Detailed source-code truncated for AI context efficiency. */

Vue Single File Component

In addition, this plugin also integrated twoslash-vue for you, so that you can also highlight Vue SFC blocks with vue twoslash:

text
/* Detailed source-code truncated for AI context efficiency. */

File System Cache

To speed up the build process, you can enable the file system cache for the generated types, that shares across multiple builds. By default the cache is stored in the .vitepress/cache/twoslash along with other VitePress caches.

In your .vitepress/config.ts:

ts [.vitepress/config.ts]
import { transformerTwoslash } from '@shikijs/vitepress-twoslash'
import { createFileSystemTypesCache } from '@shikijs/vitepress-twoslash/cache-fs' // [!code hl]
import { defineConfig } from 'vitepress'

export default defineConfig({
  markdown: {
    codeTransformers: [
      transformerTwoslash({
        typesCache: createFileSystemTypesCache() // [!code hl]
      })
    ]
  }
})

Inline Cache (experimental)

You can enable inline caching for the generated types. The @twoslash-cache: ... will auto insert into your fenced code block during development and build time.

md [./your-file.md]{2}
```ts twoslash
// @twoslash-cache: [auto generated]
const a: string = 'foo'
```

To enable the inline cache in your .vitepress/config.ts, please use the config wrapper created by createTwoslashWithInlineCache.

ts [.vitepress/config.ts]
import { createTwoslashWithInlineCache } from '@shikijs/vitepress-twoslash/cache-inline' // [!code hl]
import { defineConfig } from 'vitepress'

const withTwoslashInlineCache = createTwoslashWithInlineCache({ // [!code hl]
  // ... config of transformerTwoslash // [!code hl]
}) // [!code hl]

export default withTwoslashInlineCache( // [!code hl]
  defineConfig({
    markdown: {
      codeTransformers: [
        // move config to the `createTwoslashWithInlineCache()` // [!code error]
        // transformerTwoslash({ ... }) // [!code error]
      ]
    }
  })
) // [!code hl]

Force re-generate inline cache

To force re-generate the inline cache and ignore the existing cache, you can use the TWOSLASH_INLINE_CACHE_IGNORE environment variable when running the vitepress cli.

bash
TWOSLASH_INLINE_CACHE_IGNORE=1 vitepress dev
TWOSLASH_INLINE_CACHE_IGNORE=1 vitepress build

Remove inline cache

To remove all inline cache, you can use the TWOSLASH_INLINE_CACHE_REMOVE environment variable when running the vitepress cli.

bash
TWOSLASH_INLINE_CACHE_REMOVE=1 vitepress dev
TWOSLASH_INLINE_CACHE_REMOVE=1 vitepress build

References Engine Js Compat (`docs/references/engine-js-compat.md`)

JavaScript RegExp Engine Compatibility References

Compatibility reference of all built-in grammars with the JavaScript RegExp engine.

Generated on Friday, September 11, 2026

Version 4.4.3

Runtime: Node.js v24.16.0

Report Summary

Count
Total Languages 238
Supported 237
Mismatched 0
Unsupported 1

Supported Languages

Languages that work with the JavaScript RegExp engine, and will produce the same result as the WASM engine (with the sample snippets in the registry).
In some edge cases, it's not guaranteed that the highlighting will be 100% the same. If that happens, please create an issue with the sample snippet.

Language Highlight Match Patterns Parsable Patterns Failed Diff
abap ✅ OK 52 -
actionscript-3 ✅ OK 59 -
ada ✅ OK 200 -
ahk ✅ OK 125 -
angular-html ✅ OK 669 -
angular-ts ✅ OK 776 -
apache ✅ OK 60 -
apex ✅ OK 197 -
apl ✅ OK 912 -
applescript ✅ OK 152 -
ara ✅ OK 54 -
asciidoc ✅ OK 319 -
asm ✅ OK 298 -
astro ✅ OK 610 -
awk ✅ OK 36 -
ballerina ✅ OK 224 -
bat ✅ OK 58 -
beancount ✅ OK 42 -
berry ✅ OK 21 -
bibtex ✅ OK 19 -
bicep ✅ OK 31 -
bird2 ✅ OK 146 -
blade ✅ OK 1122 -
bsl ✅ OK 97 -
c ✅ OK 176 -
c3 ✅ OK 125 -
cadence ✅ OK 128 -
cairo ✅ OK 236 -
chapel ✅ OK 214 -
clarity ✅ OK 42 -
clojure ✅ OK 38 -
cmake ✅ OK 23 -
cobol ✅ OK 858 -
codeowners ✅ OK 4 -
codeql ✅ OK 147 -
coffee ✅ OK 466 -
common-lisp ✅ OK 60 -
coq ✅ OK 28 -
cpp ✅ OK 468 -
crystal ✅ OK 1054 -
csharp ✅ OK 315 -
css ✅ OK 141 -
csv ✅ OK 1 -
cue ✅ OK 85 -
cypher ✅ OK 39 -
d ✅ OK 272 -
dart ✅ OK 79 -
dax ✅ OK 23 -
desktop ✅ OK 16 -
diff ✅ OK 16 -
docker ✅ OK 7 -
dotenv ✅ OK 9 -
dream-maker ✅ OK 56 -
edge ✅ OK 629 -
elixir ✅ OK 704 -
elm ✅ OK 241 -
emacs-lisp ✅ OK 155 - 22
erb ✅ OK 1816 -
erlang ✅ OK 270 -
fennel ✅ OK 31 -
fish ✅ OK 34 -
fluent ✅ OK 23 -
fortran-fixed-form ✅ OK 346 -
fortran-free-form ✅ OK 339 -
fsharp ✅ OK 242 -
gdresource ✅ OK 157 -
gdscript ✅ OK 94 -
gdshader ✅ OK 39 -
genie ✅ OK 20 -
gherkin ✅ OK 19 -
gleam ✅ OK 26 -
glimmer-js ✅ OK 673 -
glimmer-ts ✅ OK 673 -
glsl ✅ OK 185 -
gn ✅ OK 19 -
gnuplot ✅ OK 82 -
go ✅ OK 126 -
graphql ✅ OK 445 -
groovy ✅ OK 130 -
hack ✅ OK 946 -
haml ✅ OK 559 -
handlebars ✅ OK 696 -
haskell ✅ OK 157 -
haxe ✅ OK 173 -
hcl ✅ OK 67 -
hjson ✅ OK 57 -
hlsl ✅ OK 52 -
html ✅ OK 608 -
http ✅ OK 746 -
hurl ✅ OK 611 -
hxml ✅ OK 182 -
hy ✅ OK 12 -
imba ✅ OK 239 -
ini ✅ OK 11 -
java ✅ OK 141 -
javascript ✅ OK 375 -
jinja ✅ OK 639 -
jison ✅ OK 431 -
json ✅ OK 19 -
json5 ✅ OK 23 -
jsonc ✅ OK 19 -
jsonl ✅ OK 19 -
jsonnet ✅ OK 33 -
jssm ✅ OK 30 -
jsx ✅ OK 375 -
julia ✅ OK 1177 -
just ✅ OK 2148 -
kdl ✅ OK 30 -
kotlin ✅ OK 58 -
kusto ✅ OK 60 -
latex ✅ OK 312 -
lean ✅ OK 31 -
less ✅ OK 279 -
liquid ✅ OK 689 -
llvm ✅ OK 25 -
log ✅ OK 31 -
logo ✅ OK 9 -
lua ✅ OK 279 -
luau ✅ OK 95 -
make ✅ OK 51 -
markdown ✅ OK 123 -
marko ✅ OK 929 -
matlab ✅ OK 88 -
mdc ✅ OK 786 -
mdx ✅ OK 197 -
mermaid ✅ OK 175 -
mipsasm ✅ OK 17 -
mojo ✅ OK 215 -
moonbit ✅ OK 101 -
move ✅ OK 113 -
narrat ✅ OK 34 -
nextflow ✅ OK 107 -
nginx ✅ OK 378 -
nim ✅ OK 1122 -
nix ✅ OK 83 -
nsis ✅ OK 38 -
nushell ✅ OK 85 -
objective-c ✅ OK 222 -
objective-cpp ✅ OK 306 -
ocaml ✅ OK 178 -
odin ✅ OK 98 -
openscad ✅ OK 24 -
org ✅ OK 4715 -
pascal ✅ OK 23 -
perl ✅ OK 937 -
php ✅ OK 1125 -
pkl ✅ OK 67 -
plsql ✅ OK 45 -
po ✅ OK 23 -
polar ✅ OK 31 -
postcss ✅ OK 49 -
powerquery ✅ OK 30 -
powershell ✅ OK 88 -
prisma ✅ OK 31 -
prolog ✅ OK 26 -
proto ✅ OK 33 -
pug ✅ OK 683 -
puppet ✅ OK 60 -
purescript ✅ OK 87 -
python ✅ OK 218 -
qml ✅ OK 407 -
qmldir ✅ OK 7 -
qss ✅ OK 31 -
r ✅ OK 66 -
racket ✅ OK 69 -
raku ✅ OK 52 -
razor ✅ OK 960 -
rbs ✅ OK 33 -
reg ✅ OK 9 -
regexp ✅ OK 34 -
rel ✅ OK 17 -
riscv ✅ OK 36 -
ron ✅ OK 35 -
rosmsg ✅ OK 31 -
rst ✅ OK 2057 -
ruby ✅ OK 1808 -
rust ✅ OK 89 -
sas ✅ OK 100 -
sass ✅ OK 69 -
scala ✅ OK 116 -
scheme ✅ OK 34 -
scss ✅ OK 233 -
sdbl ✅ OK 23 -
shaderlab ✅ OK 87 -
shellscript ✅ OK 147 -
shellsession ✅ OK 149 -
smalltalk ✅ OK 64 -
smithy ✅ OK 42 -
solidity ✅ OK 105 -
soy ✅ OK 646 -
sparql ✅ OK 19 -
splunk ✅ OK 17 -
sql ✅ OK 68 -
ssh-config ✅ OK 12 -
stata ✅ OK 254 -
stylus ✅ OK 106 -
surrealql ✅ OK 467 -
svelte ✅ OK 646 -
swift ✅ OK 340 -
system-verilog ✅ OK 102 -
systemd ✅ OK 32 -
talonscript ✅ OK 46 -
tasl ✅ OK 23 -
tcl ✅ OK 34 -
templ ✅ OK 674 -
terraform ✅ OK 68 -
tex ✅ OK 103 -
toml ✅ OK 44 -
ts-tags ✅ OK 997 -
tsv ✅ OK 1 -
tsx ✅ OK 375 -
turtle ✅ OK 15 -
twig ✅ OK 2445 -
typescript ✅ OK 362 -
typespec ✅ OK 73 -
typst ✅ OK 5699 -
v ✅ OK 77 -
vala ✅ OK 20 -
vb ✅ OK 34 -
verilog ✅ OK 34 -
vhdl ✅ OK 82 -
viml ✅ OK 72 -
vue ✅ OK 705 -
vue-html ✅ OK 404 -
vue-vine ✅ OK 1032 -
vyper ✅ OK 238 -
wasm ✅ OK 78 -
wenyan ✅ OK 18 -
wgsl ✅ OK 44 -
wikitext ✅ OK 105 -
wit ✅ OK 81 -
wolfram ✅ OK 501 -
xml ✅ OK 168 -
xsl ✅ OK 170 -
yaml ✅ OK 48 -
zenscript ✅ OK 21 -
zig ✅ OK 51 -

Table Field Explanations

  • Highlight Match: Whether the highlighting results matched with the WASM engine for the sample snippet.
  • Patterns Parsable: Number of regex patterns that can be parsed by the JavaScript RegExp engine.
  • Patterns Failed: Number of regex patterns that can't be parsed by the JavaScript RegExp engine (throws error).
  • Diff: Length of characters that are different between the highlighting results of the two engines.

Mismatched Languages

Languages that do not throw with the JavaScript RegExp engine, but will produce different results than the WASM engine.

Language Highlight Match Patterns Parsable Patterns Failed Diff

Unsupported Languages

Languages that throw with the JavaScript RegExp engine, either because they contain syntax we can't polyfill yet or because the grammar contains an invalid Oniguruma regex (that would also fail when using the WASM engine, but silently). You can try these languages with the forgiving option to skip errors.

Language Highlight Match Patterns Parsable Patterns Failed Diff
ahk2 ❌ Error 188 1 16

--- METRICS ---