Elements

Elements · Renderers · AUI

Shiki highlighter

Shiki code highlighting that defers tokenization until a message part settles.

typescript
function greet(name: string) {
  return `Hello, ${name}!`;
}

// Usage
const message = greet("World");
fig. 01

Installation

npx shadcn@latest add "@assistant-ui/shiki-highlighter"
First time? Set up a runtime

Runtime components read their state from an assistant-ui runtime. Add one to an existing project:

npx assistant-ui@latest init

Then wrap your app in a runtime provider:

import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { useChatRuntime, AssistantChatTransport } from "@assistant-ui/ai-sdk";

export default function App() {
  const runtime = useChatRuntime({
    transport: new AssistantChatTransport({ api: "/api/chat" }),
  });

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      {/* your components */}
    </AssistantRuntimeProvider>
  );
}

The installation guide covers new projects, templates, and API routes.

SyntaxHighlighter tokenizes one fenced code block with Shiki, skipping highlighting entirely while streaming is true and rendering the same plain code Shiki will later tokenize. With a runtime it derives that flag from the active message part; standalone you pass streaming yourself.

Getting started

The kit wraps the base highlighter and reads whether the current message part is still running, so it never spends a Shiki pass on code that is about to change again.

Add it to MarkdownText's components

components/assistant-ui/elements/markdown-text.tsx
import { SyntaxHighlighter } from "./shiki-highlighter.aui";

const defaultComponents = memoizeMarkdownComponents({
  SyntaxHighlighter,
  h1: /* ... */,
  // ...other elements...
});

Every fenced code block in an assistant message now highlights with Shiki, and skips tokenization for whichever block is still streaming.

Anatomy

<div className="aui-shiki-base">
  {/* streaming: <pre><code>{code}</code></pre>, unhighlighted */}
  {/* settled: useShikiHighlighter's tokenized output */}
</div>

While streaming, the root also gets an aui-shiki-streaming class and renders the trimmed source as a plain <pre><code>. Once streaming is false, useShikiHighlighter tokenizes the code; until that resolves, the same plain fallback shows, so there is no flash of empty content between the two states.

Examples

Set a fixed theme

<SyntaxHighlighter language="tsx" code={code} theme="github-dark-default" />

The default is { dark: "github-dark-default", light: "github-light-default" }. Pass a single theme name to fix one theme in both modes, or a { light, dark, ... } map for multi-theme output.

Tune the streaming delay

<SyntaxHighlighter language="tsx" code={code} delay={0} />

delay (default 150) is how long, in milliseconds, useShikiHighlighter waits after the code last changed before tokenizing. The default absorbs the last few characters a smooth text reveal drains in after the underlying part has already settled.

Restyle the container

Both lanes take className on the root; the rendered <pre> picks up border, background, and padding from the fixed aui-shiki-base selectors.

<SyntaxHighlighter className="[&_pre]:rounded-none" language="tsx" code={code} />

API reference

Message part state

SelectorTypeDescription
s.optional.part?.status.type === "running"booleanRead with useAuiState. Forwarded as streaming. true while the current message part exists and is running.

Every other prop below is forwarded unchanged. node and components (react-markdown's code-block wrapper props) are accepted for type compatibility with MarkdownText's SyntaxHighlighter slot and otherwise unused.

SyntaxHighlighter

PropTypeDefaultDescription
codestringrequiredThe code to highlight. Trimmed before rendering.
languageShikiHighlighterProps["language"]requiredPassed to useShikiHighlighter.
themeShikiHighlighterProps["theme"]{ dark: "github-dark-default", light: "github-light-default" }A single theme name or a { light, dark, ... } map.
streamingbooleanfalseSkips tokenization and renders the plain code while true.
delaynumber150Milliseconds useShikiHighlighter waits after code last changed before tokenizing.
classNamestringMerged onto the root.

Every other react-shiki option (showLineNumbers, langStyle, Shiki's own codeToHast options, and so on) forwards straight to useShikiHighlighter, except addDefaultStyles and showLanguage, which react-shiki uses only for its own default styling and language label; this component supplies neither, so both are accepted and ignored.