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.
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
Overview (`packages/cli/README.md`)
shiki-cli
Shiki in the command line.
License
MIT
Overview (`packages/codegen/README.md`)
@shikijs/codegen
Codegen for fine-grained Shiki bundles.
License
MIT
Overview (`packages/colorized-brackets/README.md`)
@shikijs/colorized-brackets
VSCode-style colorized brackets transformer for Shiki.
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.
License
MIT
Overview (`packages/engine-oniguruma/README.md`)
@shikijs/engine-oniguruma
Engine for Shiki using Oniguruma RegExp engine in WebAssembly.
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.
License
MIT
Overview (`packages/markdown-exit/README.md`)
@shikijs/markdown-exit
markdown-exit plugin for shiki
License
MIT
Overview (`packages/markdown-it/README.md`)
@shikijs/markdown-it
markdown-it plugin for shiki
License
MIT
Overview (`packages/monaco/README.md`)
@shikijs/monaco
Use shiki to highlight Monaco Editor.
License
MIT
Overview (`packages/rehype/README.md`)
rehype-shiki
License
MIT
Overview (`packages/shiki/README.md`)
Shiki 式
A beautiful syntax highlighter based on TextMate grammars, accurate and powerful.
License
Overview (`packages/stream/README.md`)
@shikijs/stream
Streaming colorization for shiki. Useful for highlighting text streams like LLM outputs.
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.
License
MIT
Overview (`packages/twoslash/README.md`)
@shikijs/twoslash
A shiki transformer for TypeScript Twoslash.
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.
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
- All grammars/themes/wasm served as ESM, lazy-loaded on demand and bundler-friendly.
- Portable & agnostic. Does not rely on Node.js APIs or the filesystem, works in any modern JavaScript runtime.
- ESM-only (CDN Usage, CJS Usage).
- Bundles languages/themes composedly.
- Light/Dark themes support
hastsupport- Transformers API
- Decorations API
- TypeScript Twoslash integrations
- Compatible build
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.
/* Detailed source-code truncated for AI context efficiency. */{
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 totextmate-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:
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:
[0;32mcolored foreground[0m
[0;42mcolored background[0m
[0;1mbold text[0m
[0;2mdimmed text[0m
[0;4munderlined text[0m
[0;7mreversed text[0m
[0;9mstrikethrough text[0m
[0;4;9munderlined + strikethrough text[0mCheck 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 totextmate-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:
// @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:
npx covolute@latest shiki/v1-to-v2Deprecations
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.
WASM Related APIs
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:
import { loadWasm } from 'shiki' // [!code --]
import { loadWasm } from 'shiki/engine/oniguruma' // [!code ++]loadWasm field in createHighlighterCore is replaced with engine field:
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.
Other Deprecations
createdBundledHighlighterrequires a single object-style argument@shikijs/core- The regex engines
createJavaScriptRegexEnginecreateOnigurumaEngineare no longer included, import them from@shikijs/engine-onigurumaand@shikijs/engine-javascriptrespectively createHighlighterCorenow explicits requires anenginefield to be passedloadWasmfield increateHighlighterCoreis replaced withenginefield@shikijs/core/wasm-inlineis replaced with@shikijs/engine-oniguruma/wasm-inline- Import
FontStyleandStackElementMetadatafrom@shikijs/vscode-textmateinstead of@shikijs/core
- The regex engines
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:
import { enableDeprecationWarnings } from 'shiki/core'
enableDeprecationWarnings(true, true) // enable warnings and throw errors
// use crateHighlighter(...) etc. after thatDisable Warnings
If you want to disable the warnings:
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!
{
"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.
WASM Related APIs
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:
import { loadWasm } from 'shiki' // [!code --]
import { loadWasm } from 'shiki/engine/oniguruma' // [!code ++]loadWasm field in createHighlighterCore is replaced with engine field:
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.
Other Breaking Changes
createdBundledHighlighterrequires a single object-style argument@shikijs/core- The regex engines
createJavaScriptRegexEnginecreateOnigurumaEngineare no longer included, import them from@shikijs/engine-onigurumaand@shikijs/engine-javascriptrespectively @shikijs/core/wasm-inlineis replaced with@shikijs/engine-oniguruma/wasm-inline- Import
FontStyleandStackElementMetadatafrom@shikijs/vscode-textmateinstead of@shikijs/core
- The regex engines
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.
{
"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:
{
"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:
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:
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:
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:
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.)
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.
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.
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:
/* 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.
/* 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:
{
"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.
/* Detailed source-code truncated for AI context efficiency. */The result will be (styled with CSS in this example):
// @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:
/* Detailed source-code truncated for AI context efficiency. */It renders:
// @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:
// @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:
// @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:
/* 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.
/* Detailed source-code truncated for AI context efficiency. */The following HTML will be generated (demo preview):
<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
@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
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.
/* Detailed source-code truncated for AI context efficiency. */span tokens would be generated with respective theme's css variables:
<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:
[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;
}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.
/* Detailed source-code truncated for AI context efficiency. */With it, a token would be generated like:
<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.
/* 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:
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:
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:
const shiki = await getHighlighter(/* ... */)
const root = shiki.codeToHast(/* ... */)
const grammarState = shiki.getLastGrammarState(root) // pass the hast root instead of codeGuide Install (`docs/guide/install.md`)
outline: deep
Installation & Usage
Installation
Install via npm, or see CDN Usage:
::: code-group
npm install -D shikiyarn add -D shikipnpm add -D shikibun add -D shikideno add npm:shiki:::
Integrations
We also provide some integrations:
- markdown-it Plugin
- Rehype Plugin
- TypeScript Twoslash Integration
- Monaco Editor Syntax Highlight
- CLI
- Common Transformers
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.
/* 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:
// @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'
})// @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.
/* 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.
// @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.
/* 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.
/* 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:
/* Detailed source-code truncated for AI context efficiency. */Can be written in CJS as:
/* 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.
<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.
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.
/* 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.
/* Detailed source-code truncated for AI context efficiency. */You can also load languages after the highlighter has been created.
/* 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:
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.):
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:
/* 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.
/* Detailed source-code truncated for AI context efficiency. */You can also load themes after the highlighter has been created.
/* 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.
codeToHtmluseshastinternally. The generated HTML will be a bit different but should behave the same.css-variablestheme 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,setWasmare dropped as they are not needed anymore. BUNDLED_LANGUAGES,BUNDLED_THEMESare moved to@shikijs/langsand@shikijs/themesand renamed tobundledLanguagesandbundledThemesrespectively.themeoption forcreateHighlighteris dropped, usethemeswith an array instead.- Highlighter does not maintain an internal default theme context.
themeoption is required forcodeToHtmlandcodeToTokens. codeToThemedTokensis renamed tocodeToTokensBase, a higher levelcodeToTokensis added.codeToTokenssetsincludeExplanationtofalseby default..ansiToHtmlis merged into.codeToHtmlas a special language,ansi. Use.codeToHtml(code, { lang: 'ansi' })instead.lineOptionsis dropped in favor of the fully customizabletransformsoption.LanguageRegistration'sgrammarfield is flattened toLanguageRegistrationitself, refer to the types for more details.
Ecosystem Packages
shiki-twoslashhas 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, likegatsby-remark-shiki-twoslashetc, will be slowly moved to a general Shiki version. Before that, you can use@shikijs/rehypeor@shikijs/markdown-itto integrate Shiki into those meta-frameworks. - New official integrations like
@shikijs/monaco,@shikijs/cli,@shikijs/rehype,@shikijs/markdown-itare introduced. shiki-renderer-pathandshiki-renderer-svgpackages 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-shikiis 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->shikishikiji-core->@shikijs/coreshikiji-twoslash->@shikijs/twoslashshikiji-transformers->@shikijs/transformersshikiji-monaco->@shikijs/monacoshikiji-cli->@shikijs/climarkdown-it-shikiji->@shikijs/markdown-itrehype-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:
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.
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.
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:
const jsEngine = createJavaScriptRegexEngine({ forgiving: true })
// ...use the engineThis 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:
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:
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.
/* 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:
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.
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.
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:
/* 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:
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:
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:
/* Detailed source-code truncated for AI context efficiency. */CSS variables example:
/* 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:
/* 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
flowchart LR
preprocess --> tokens
tokens --> span
span --> span
span --> line
line --> span
line --> code
code --> pre
pre --> root
root --> postprocesspreprocess- 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 incodeToHast.
Meta
Transformers can also access markdown 'meta' strings in supported integrations.
```html foo=bar baz-qux="qu ux"You can access the raw meta using:
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:
// @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.
npx @shikijs/cli README.mdnpx @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
npm i @shikijs/cliyarn global add @shikijs/clipnpm add @shikijs/clibun add -g @shikijs/clideno install -gREn skat npm:@shikijs/cli:::
skat src/index.tsOptions
`--theme`
Specify the theme to use. Defaults to vitesse-dark.
npx @shikijs/cli README.md --theme=nord`--lang`
Language is auto-inferred from the file extension. You can override it with --lang.
npx @shikijs/cli src/index.js --lang=ts`--format`
Specify the output format. Defaults to ansi.
Supported values: ansi, html.
npx @shikijs/cli README.md --format=htmlNode.js API
The @shikijs/cli package also provides a Node.js API.
::: code-group
npm i @shikijs/cliyarn add @shikijs/clipnpm add @shikijs/clibun add @shikijs/clideno 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.
import { codeToANSI } from '@shikijs/cli'
const highlighted = await codeToANSI(source, 'typescript', 'nord')
console.log(highlighted)codeToANSI takes three required parameters:
code: stringlang: BundledLanguagetheme: BundledTheme
and one optional parameter:
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:
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
npx shiki-codegen \
--langs typescript,javascript,vue \
--themes light-plus,dark-plus \
--engine javascript \
./shiki.bundle.tsFile shiki.bundle.ts will be created with the code that you can use in your project.
Then you can use it in your project:
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:
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 filePackages Colorized Brackets (`docs/packages/colorized-brackets.md`)
outline: deep
@shikijs/colorized-brackets
VSCode-style colorized brackets transformer for Shiki.
Install
::: code-group
npm i -D @shikijs/colorized-bracketsyarn add -D @shikijs/colorized-bracketspnpm add -D @shikijs/colorized-bracketsbun add -D @shikijs/colorized-bracketsdeno add npm:@shikijs/colorized-brackets:::
Usage
Add to your Shiki transformers:
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:
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:
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:
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:
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:
const transformer = transformerColorizedBrackets({
explicitTrigger: true,
})Then, only code blocks with the colorize-brackets meta string will have bracket colorizing enabled.
```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 whenevercodechangesShikiMagicMovePrecompiled— animations for compiled tokens, without the Shiki dependency at runtimeShikiMagicMoveRenderer— 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
npm i -D @shikijs/magic-move shikiyarn add -D @shikijs/magic-move shikipnpm add -D @shikijs/magic-move shikibun add -D @shikijs/magic-move shikideno add npm:@shikijs/magic-move npm:shiki:::
Usage
Vue
Import @shikijs/magic-move/vue and pass the highlighter instance to the ShikiMagicMove component.
<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.
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.
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.
<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.
<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:
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
npm i -D @shikijs/markdown-exityarn add -D @shikijs/markdown-exitpnpm add -D @shikijs/markdown-exitbun add -D @shikijs/markdown-exitdeno 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:
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.
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:
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:
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
npm i -D @shikijs/markdown-ityarn add -D @shikijs/markdown-itpnpm add -D @shikijs/markdown-itbun add -D @shikijs/markdown-itdeno add npm:@shikijs/markdown-it:::
Usage
/* Detailed source-code truncated for AI context efficiency. */Transformers
You can pass transformers to the plugin options to customize the highlighted code.
/* 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:
/* 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.
/* 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
<code>
…
</code>the result after markdown-it processing will be
<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
npm i -D @shikijs/monacoyarn add -D @shikijs/monacopnpm add -D @shikijs/monacobun add -D @shikijs/monacodeno add npm:@shikijs/monaco:::
Usage
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 normallymodern-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
npm i -D modern-monacoyarn add -D modern-monacopnpm add -D modern-monacobun add -D modern-monacodeno add npm:modern-monaco:::
Or import it from esm.sh CDN in the browser without a build step:
import * as monaco from 'https://esm.sh/modern-monaco'Usage
<monaco-editor theme="vitesse-dark"></monaco-editor>
<script src="app.js" type="module"></script>// 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.
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.
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:
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:
'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:
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.
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:
nuxt-shiki- Shiki integration for client and server sides, with component usage.@nuxt/content- Nuxt Content has Shiki built-in for markdown files. You can enable it with thehighlightoption.@nuxtjs/mdc- The underlying module of@nuxt/contentthat provides Markdown Component (MDC) syntax as well as Shiki for syntax highlighting.
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
npm i -D @shikijs/rehypeyarn add -D @shikijs/rehypepnpm add -D @shikijs/rehypebun add -D @shikijs/rehypedeno add npm:@shikijs/rehype:::
Usage
/* 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:
/* 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:
/* Detailed source-code truncated for AI context efficiency. */Then you can use inline code in markdown:
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:
- Converts the HAST fragment to HTML using
hast-util-to-html - Runs your HTML transformers
- Converts back to HAST using
hast-util-from-html
Example:
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
npm i -D @shikijs/streamyarn add -D @shikijs/streampnpm add -D @shikijs/streambun add -D @shikijs/streamdeno add npm:@shikijs/stream:::
Usage
Create a transform stream with CodeToTokenTransformStream and .pipeThrough your text stream:
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:
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
tokensStream.pipeTo(new WritableStream({
async write(token) {
console.log(token)
},
}))Or in Node.js:
for await (const token of tokensStream) {
console.log(token)
}Vue
<script setup lang="ts">
import { ShikiStreamRenderer } from '@shikijs/stream/vue'
// get the token stream
</script>
<template>
<ShikiStreamRenderer :stream="tokensStream" />
</template>React
import { ShikiStreamRenderer } from '@shikijs/stream/react'
export function MyComponent() {
// get the token stream
return <ShikiStreamRenderer stream={tokensStream} />
}Solid
import { ShikiStreamRenderer } from '@shikijs/stream/solid'
export function MyComponent() {
// get the token stream
return <ShikiStreamRenderer stream={tokensStream} />
}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
<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
npm i -D @shikijs/transformersyarn add -D @shikijs/transformerspnpm add -D @shikijs/transformersbun add -D @shikijs/transformersdeno add npm:@shikijs/transformers:::
Usage
/* 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.
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:
// [\!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:
// [\!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.
```ts
console.log('hewwo') // [\!code --]
console.log('hello') // [\!code ++]
console.log('goodbye')
```Renders (with custom CSS rules):
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
<code>
function(){
console.log('hewwo')
console.log('hello')
}
</code>:::
`transformerNotationHighlight`
Use [!code highlight] to highlight a line.
```ts
console.log('Not highlighted')
console.log('Highlighted') // [\!code highlight]
console.log('Not highlighted')
```Renders (with custom CSS rules):
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:
```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:
// [!code highlight:3]
console.log('Highlighted')
console.log('Highlighted')
console.log('Highlighted')
console.log('Not highlighted')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.
```ts
// [\!code word:Hello]
const message = 'Hello World'
console.log(message) // prints Hello World
```Renders (with custom CSS rules):
// [!code word:Hello]
const message = 'Hello World'
console.log(message) // prints Hello WorldOutputs: <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.
```ts
// [\!code word:Hello:1]
const message = 'Hello World'
console.log(message) // prints Hello World
```Renders:
// [!code word:Hello:1]
const message = 'Hello World'
console.log(message) // prints Hello World`transformerNotationFocus`
Use [!code focus] to focus a line.
```ts
console.log('Not focused');
console.log('Focused') // [\!code focus]
console.log('Not focused');
```Renders (with custom CSS rules):
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:
```ts
// [\!code focus:3]
console.log('Focused')
console.log('Focused')
console.log('Focused')
console.log('Not focused')
```Renders:
// [!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.
```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:
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:
function block( ) {
space( )
tab( )
}::: details Example 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:
function func() {
console.log(1);
for (const i of []) {
console.log(2);
}
}::: details Example 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.
import { transformerRenderLineNumber } from '@shikijs/transformers'
const html = await codeToHtml(code, {
transformers: [
transformerRenderLineNumber(),
],
})Options:
start: Start number. Default1.classLineNumber: Class for line number. Default'line-number'.
With some additional CSS rules, you can make it look like this:
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.
```js {1,3-4}
console.log('1')
console.log('2')
console.log('3')
console.log('4')
```Renders (with custom CSS rules):
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.
```js /Hello/
const msg = 'Hello World'
console.log(msg)
console.log(msg) // prints Hello World
```Renders (with custom CSS rules):
const msg = 'Hello World'
console.log(msg) // prints Hello WorldOutputs: <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:
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 appHTML output:
<code>
console
.
log
(
'
hello
'
)
</code>CSS output:
.__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.
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. Defaulttrue.
For example:
```js
// This is a comment
const x = 1 // Inline comment
/* Block comment */
const y = 2
// Another comment
```Will renders:
const x = 1
const y = 2Packages Twoslash (`docs/packages/twoslash.md`)
outline: deep
@shikijs/twoslash
A Shiki transformer for Twoslash, provide inline type hover inside code blocks.
Install
::: code-group
npm install -D @shikijs/twoslashyarn add -D @shikijs/twoslashpnpm add -D @shikijs/twoslashbun add -D @shikijs/twoslashdeno 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.
/* 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`
::: 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.
// @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:
/* Detailed source-code truncated for AI context efficiency. *//* Detailed source-code truncated for AI context efficiency. */`rendererClassic`
This renderer aligns with the output of legacy shiki-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`
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.
// @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 // <--
})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:
// 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
npm i -D @shikijs/vitepress-twoslashyarn add -D @shikijs/vitepress-twoslashpnpm add -D @shikijs/vitepress-twoslashbun add -D @shikijs/vitepress-twoslashdeno add npm:@shikijs/vitepress-twoslash:::
In your .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.
/* 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:
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.
```ts twoslash
console.log('hello')
// ^?
```It will be rendered as:
/* 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:
/* 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:
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.
```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.
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.
TWOSLASH_INLINE_CACHE_IGNORE=1 vitepress dev
TWOSLASH_INLINE_CACHE_IGNORE=1 vitepress buildRemove inline cache
To remove all inline cache, you can use the TWOSLASH_INLINE_CACHE_REMOVE environment variable when running the vitepress cli.
TWOSLASH_INLINE_CACHE_REMOVE=1 vitepress dev
TWOSLASH_INLINE_CACHE_REMOVE=1 vitepress buildReferences 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.3Runtime: 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 ---
- Files Extracted: 62
- Estimated Token Budget: ~43211 tokens
- Canonical Reference: https://codewiki.google/github.com/shikijs/shiki