---
title: Syntax highlighting
description: Add optional Shiki highlighting, language labels, and copy controls with @smoothstream/code.
---

Syntax highlighting is a separate package so applications that do not need Shiki do not download it. Without a highlighter, fenced blocks render as ordinary `<pre><code>` with no toolbar.

Install `@smoothstream/code` next to your adapter and pass `codeHighlighter`.

<CodeGroup>
```bash title="npm" hideLanguageIcon
npm install @smoothstream/code
```

```bash title="pnpm" hideLanguageIcon
pnpm add @smoothstream/code
```

```bash title="yarn" hideLanguageIcon
yarn add @smoothstream/code
```

```bash title="bun" hideLanguageIcon
bun add @smoothstream/code
```
</CodeGroup>

<Tabs>
  <Tab label="React" icon="/icons/react.svg">
    ```tsx
    import { Smoothstream } from "@smoothstream/react";
    import { codeHighlighter } from "@smoothstream/code";

    <Smoothstream codeHighlighter={codeHighlighter} receiving={receiving}>
      {text}
    </Smoothstream>
    ```
  </Tab>
  <Tab label="Vue" icon="/icons/vue.svg">
    ```vue
    <script setup lang="ts">
    import { Smoothstream } from "@smoothstream/vue";
    import { codeHighlighter } from "@smoothstream/code";
    </script>

    <template>
      <Smoothstream
        :markdown="text"
        :receiving="receiving"
        :code-highlighter="codeHighlighter"
      />
    </template>
    ```
  </Tab>
  <Tab label="Vanilla" icon="/icons/javascript-typescript.svg">
    ```ts
    import { createSmoothstream } from "@smoothstream/dom";
    import { codeHighlighter } from "@smoothstream/code";

    createSmoothstream(container, {
      receiving: true,
      codeHighlighter,
    });
    ```
  </Tab>
</Tabs>

Attaching a highlighter also opts fenced blocks into Smoothstream's code surface: a language label and a copy button. The copy control mounts when the block is confirmed complete, including when input closes without an explicit closing fence. It copies the full raw code even if the visual reveal is still running.

## Choose a theme

`codeHighlighter` is a ready-made instance using Shiki's `github-light` theme. Create your own for a different bundled theme, hidden labels, or paired light and dark themes.

```ts
import { createCodeHighlighter } from "@smoothstream/code";

const codeHighlighter = createCodeHighlighter({
  theme: "vitesse-light",
});
```

```ts
const codeHighlighter = createCodeHighlighter({
  showLanguageLabels: false,
});
```

`theme` and `themes` are mutually exclusive. For applications with light and dark color schemes, pass Shiki's `themes` API:

```ts
const codeHighlighter = createCodeHighlighter({
  themes: {
    light: "vitesse-light",
    dark: "vitesse-dark",
  },
  defaultColor: "light-dark()",
});
```

`defaultColor` follows Shiki: `"light"` (default), `"dark"`, `"light-dark()"`, or `false`. The `light-dark()` strategy follows the nearest CSS `color-scheme`. Class-based toggles should update that property:

```css
html {
  color-scheme: light;
}

html.dark {
  color-scheme: dark;
}
```

Token styles also keep Shiki's `--shiki-light` and `--shiki-dark` variables if you prefer selector-based switching.

<Callout type="info" title="What loads in the browser">
  Bundled themes and grammars are lazy. The app can ship the Shiki registry, but the browser fetches only the selected theme and the languages Smoothstream actually encounters. Unknown fence languages keep the theme palette and fall back to unhighlighted code instead of blocking playback. Line colors become immutable before the first character of that line is revealed.
</Callout>

## Reuse the highlighter

Create a configured highlighter once, outside component render functions. It caches theme and language loading and separates tokenizer state by code-block session. The same instance can serve multiple responses.

Use paired themes for a live color-scheme toggle instead of replacing the highlighter during a response. Previously accepted code lines preserve their styles; a dual-theme result already contains the values CSS needs for both appearances.

For label size, copy-button dimensions, or code-surface overrides, see [CSS variables](/reference/css-variables). For a different tokenizer or custom grammar-loading infrastructure, implement a [custom highlighter](/extensions/custom-highlighters).
