A Storybook addon that replaces Storybook's syntax highlighting with Shiki for beautiful, accurate syntax highlighting β with zero changes to your stories.
- π Framework-agnostic β works with React, Vue, Angular, Svelte, or any other Storybook framework.
- π¦ Fine-grained bundling β Shiki loads only the languages and themes you need, keeping your bundle lean.
- π Drop-in β uses runtime DOM patching; no fragile module aliasing or bundler hacks.
- π‘οΈ Future-proof β resilient to Storybook internal changes since it operates at the DOM level.
- π¨ Full transformer support β supports all Shiki transformers (notation highlights, diffs, focus, etc.).
- π’ Line numbers β full support for line numbers in code blocks.
- π Copy button β optional copy-to-clipboard functionality.
- β Supports Vite and Webpack 5 builders.
- π― Rich transformers β Built-in support for diffs, highlights, focus, and more
- π BYO CSS β Bring your own styles for the custom transformers (diffs, focusing, highlighting)
This addon directly addresses storybookjs/storybook#29160. Storybook's default syntax highlighting relies on Prism.js which contributes significantly to bundle size.
By using a runtime DOM patching approach, this addon is also future-proof and won't break when Storybook changes its internal module structure.
pnpm add -D @lukethacoder/storybook-addon-shiki shiki
# or
npm install --save-dev @lukethacoder/storybook-addon-shiki shiki
# or
yarn add -D @lukethacoder/storybook-addon-shiki shikiPeer dependency:
shiki >= 1.0.0is required and must be installed separately. This keeps you in control of the Shiki version.
View on npm
Register the addon in .storybook/main.ts:
// .storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite'; // or your framework
const config: StorybookConfig = {
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
addons: [
'@storybook/addon-essentials',
'@lukethacoder/storybook-addon-shiki',
],
framework: '@storybook/react-vite',
};
export default config;That's it. Start Storybook and every code block β in the Docs tab, the Source panel, your MDX pages β is now powered by Shiki.
This extensions does not bring its own CSS. You bring your own, you configure it how you like. If you want a drop in "it just works" CSS file, see the ./.storybook/styles.css file.
By default the addon uses vitesse-dark. Pass a shiki key when registering the addon to customise:
// .storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite';
const config: StorybookConfig = {
addons: [
'@storybook/addon-essentials',
{
name: '@lukethacoder/storybook-addon-shiki',
options: {
shiki: {
// any valid shiki theme - `import { type BundledLanguage } from 'shiki'`
theme: 'one-dark-pro',
// BYO list of languages that you wish to use
langs: [
'jsx',
'tsx',
'typescript',
'javascript',
'css',
'html',
'bash',
'json',
'yaml',
'markdown',
'graphql',
'svelte',
],
// requires installing `@shikijs/transformers`
transformers: {
// Enable notation-based features (use comments like [!code highlight])
notationHighlight: true,
notationDiff: true,
notationFocus: true,
notationErrorLevel: true,
notationWordHighlight: true,
// Enable meta-based features (use meta strings like {1-3})
focus: true,
highlight: true,
// Enable diff indicators
diff: true,
// Enable colorized brackets - requires installing `@shikijs/colorized-brackets`
colorizedBrackets: true,
},
},
},
},
],
};
export default config;Browse all available themes at shiki.style/themes.
Shiki loads language grammars on-demand. To ensure specific languages are available immediately and avoid any first-render delay, declare them upfront:
{
name: '@lukethacoder/storybook-addon-shiki',
options: {
shiki: {
theme: 'one-dark-pro',
langs: ['tsx', 'typescript', 'bash', 'json', 'yaml', 'rust', 'python'],
},
},
}The following languages are pre-loaded by default: jsx, tsx, typescript, javascript, css, html, bash, json, markdown.
/**
* Options for configuring the Shiki addon.
* These can be set in .storybook/main.ts when registering the addon,
* or overridden per-story via parameters.shiki.
*/
export interface ShikiAddonOptions {
/**
* The Shiki theme to use for syntax highlighting.
* @default 'vitesse-dark'
*/
theme?: BundledTheme | string;
/**
* Languages to pre-load. Shiki loads grammars lazily by default; listing
* the languages your docs use here avoids any first-render flash.
*
* @default Common web languages are loaded automatically.
*/
langs?: (BundledLanguage | string)[];
/**
* Provide your own pre-built Shiki highlighter instance. When supplied the
* addon will use it as-is and skip its own initialisation. Useful if you
* already create a highlighter in your Storybook config.
*/
highlighter?: HighlighterGeneric<BundledLanguage, BundledTheme>;
/**
* Configure which Shiki transformers to enable globally.
* Requires @shikijs/transformers to be installed.
*
* @example
* ```ts
* transformers: {
* focus: true,
* highlight: true,
* diff: true,
* }
* ```
*/
transformers?: TransformerConfig;
}This addon uses a pure runtime approach with client-side DOM patching β no fragile module aliasing, no bundler hacks. This makes it incredibly resilient to Storybook internal changes.
The addon includes a runtime patcher that runs entirely in the browser:
- Storybook renders normally β Code blocks render using Storybook's default Prism syntax highlighting
- DOM observer activates β A
MutationObserverwatches for PrismJS code blocks as they're added to the DOM - Code extraction β When a Prism block is detected, the addon extracts the raw code text (preserving any transformer notation like
[!code highlight]) - Shiki re-rendering β The code is re-highlighted using Shiki with your configured theme and transformers
- DOM replacement β The original Prism element is hidden (
display: none) and the Shiki version is inserted as a sibling - Content watching β The addon continues to watch the hidden Prism elements for content changes and automatically regenerates Shiki output when needed
- Cleanup β When Prism elements are removed from the DOM, their corresponding Shiki versions are cleaned up automatically
This approach ensures comprehensive coverage:
- β Docs tab "Show code" blocks
- β
MDX code blocks (
```jsx) - β
Source panel (
<Source />blocks) - β Controls panel (argTypes with control descriptions)
- β Code Panel (in the manager context)
- β Autodocs (auto-generated documentation)
- β Dynamic content stays in sync as stories change
Previous approaches relied on bundler-level module aliasing (proxying @storybook/addon-docs/blocks, etc.) which was:
- β Fragile and broke with Storybook internal changes
- β Complex to maintain across different bundler configurations
- β Difficult to debug when issues occurred
Runtime patching is:
- β Resilient β Works regardless of how Storybook's internals change
- β Simple β One clear interception point in the DOM
- β Universal β Works with any bundler (Vite, Webpack, etc.)
- β Debuggable β All logic is in one place and runs in the browser
Addon options configured in main.ts are made available to both preview and manager contexts:
- Preview context: Via a virtual module (
@lukethacoder/storybook-addon-shiki/options) generated by the preset'sviteFinal/webpackFinalhooks - Manager context: Via a global variable (
window.__STORYBOOK_ADDON_SHIKI_OPTIONS__) injected through themanagerHeadhook
This ensures consistent theming and configuration across all code blocks, regardless of where they render.
git clone https://github.com/lukethacoder/storybook-addon-shiki
cd storybook-addon-shiki
# install packages
pnpm install
# compile with tsup
pnpm build
# start the dev Storybook (uses the addon on itself)
pnpm storybook Manual package updating
pnpm whoami
pnpm login
pnpm publish --access public # add --tag TAG_NAME if required| Storybook | Builder | Status |
|---|---|---|
| 8.x | Vite | β |
| 8.x | Webpack 5 | β |
| 9.x / 10.x | Vite | β |
| 9.x / 10.x | Webpack 5 | β |
MIT