Skip to main content
The @streamdown/code plugin provides syntax highlighting for fenced code blocks using Shiki. Languages are lazy-loaded on demand and token results are cached, so rendering stays fast even when many different languages appear in a stream.

Installation

1

Install the package

npm install @streamdown/code
2

Configure Tailwind CSS

The package ships Tailwind utility classes in its dist bundle. Add a source directive so Tailwind can scan them.
globals.css
@source "../node_modules/@streamdown/code/dist/*.js";
Adjust the ../ prefix so the path resolves from your CSS file to node_modules. In a monorepo you may need ../../node_modules/....
3

Pass the plugin to Streamdown

import { Streamdown } from 'streamdown';
import { code } from '@streamdown/code';

export default function Chat({ markdown }: { markdown: string }) {
  return (
    <Streamdown plugins={{ code }}>
      {markdown}
    </Streamdown>
  );
}

Theme configuration

The plugin accepts a themes tuple: [lightTheme, darkTheme]. Streamdown reads the active color scheme from the user’s system preference and applies the matching theme.
import { createCodePlugin } from '@streamdown/code';

const code = createCodePlugin({
  themes: ['github-light', 'github-dark'], // default
});
You can also override themes at the component level via the shikiTheme prop:
<Streamdown
  plugins={{ code }}
  shikiTheme={['one-light', 'one-dark-pro']}
>
  {markdown}
</Streamdown>
When a code plugin is present, plugins.code.getThemes() takes precedence over the shikiTheme prop.

Bundled themes

Any value from Shiki’s BundledTheme type is accepted by name (e.g. 'github-light', 'dracula', 'nord'). The full list is available in the Shiki theme gallery.

Custom themes

You can pass a ThemeRegistrationAny object directly instead of a theme name:
import { createCodePlugin } from '@streamdown/code';
import type { ThemeRegistrationAny } from '@streamdown/code';

const myTheme: ThemeRegistrationAny = {
  name: 'my-theme',
  // TextMate theme JSON fields
  colors: {
    'editor.background': '#1a1a2e',
    'editor.foreground': '#e0e0e0',
  },
  tokenColors: [
    {
      scope: ['comment'],
      settings: { foreground: '#6a737d', fontStyle: 'italic' },
    },
  ],
};

const code = createCodePlugin({
  themes: ['github-light', myTheme],
});

Supported languages

The plugin supports every language bundled with Shiki (200+). Use getSupportedLanguages() to get the full list at runtime:
import { code } from '@streamdown/code';

const languages = code.getSupportedLanguages();
// BundledLanguage[] — e.g. ['javascript', 'typescript', 'python', ...]
To check a specific language before rendering:
if (code.supportsLanguage('gleam')) {
  // safe to use
}
Unknown language identifiers fall back to plain text without throwing.

API reference

createCodePlugin(options?)

Returns a CodeHighlighterPlugin instance.
interface CodePluginOptions {
  /**
   * Default themes for syntax highlighting [light, dark]
   * @default ["github-light", "github-dark"]
   */
  themes?: [ThemeInput, ThemeInput];
}

CodeHighlighterPlugin

interface CodeHighlighterPlugin {
  name: 'shiki';
  type: 'code-highlighter';
  getSupportedLanguages: () => BundledLanguage[];
  getThemes: () => [ThemeInput, ThemeInput];
  highlight: (
    options: HighlightOptions,
    callback?: (result: HighlightResult) => void
  ) => HighlightResult | null;
  supportsLanguage: (language: BundledLanguage) => boolean;
}
highlight() returns null when the Shiki highlighter is still loading asynchronously. Pass a callback to receive the result once it is ready — Streamdown handles this automatically via its internal subscriber pattern.

Exported types

import type {
  BundledLanguage,       // All Shiki-bundled language IDs
  BundledTheme,          // All Shiki-bundled theme names
  ThemeRegistrationAny,  // Custom TextMate theme object
  ThemeInput,            // BundledTheme | ThemeRegistrationAny
  HighlightOptions,
  HighlightResult,
  CodeHighlighterPlugin,
  CodePluginOptions,
} from '@streamdown/code';

Controls

Code block controls (copy and download buttons) are enabled by default. You can configure them via the controls prop:
<Streamdown
  plugins={{ code }}
  controls={{
    code: {
      copy: true,
      download: true,
    },
  }}
>
  {markdown}
</Streamdown>
Set controls={{ code: false }} to hide all code block controls, or controls={false} to hide controls globally.

Pre-configured instance

For convenience, @streamdown/code exports a pre-configured instance with the default ['github-light', 'github-dark'] theme pair:
import { code } from '@streamdown/code';
// Equivalent to: createCodePlugin()

Build docs developers (and LLMs) love