Elements · Renderers · AUI
Shiki highlighter
Shiki code highlighting that defers tokenization until a message part settles.
function greet(name: string) {
return `Hello, ${name}!`;
}
// Usage
const message = greet("World");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 initThen 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.
npx shadcn@latest add "@assistant-ui/elements-shiki-highlighter"Props-driven: no runtime or provider required.
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
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.
Standalone, you own the streaming flag yourself.
Render code with a controlled streaming flag
import { SyntaxHighlighter } from "@/components/assistant-ui/elements/shiki-highlighter";
<SyntaxHighlighter language="tsx" code={code} streaming={isStreaming} />;While streaming is true, the code renders as plain, unhighlighted text in the same container; flip it to false once the source is final and it tokenizes.
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
| Selector | Type | Description |
|---|---|---|
s.optional.part?.status.type === "running" | boolean | Read 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
| Prop | Type | Default | Description |
|---|---|---|---|
code | string | required | The code to highlight. Trimmed before rendering. |
language | ShikiHighlighterProps["language"] | required | Passed to useShikiHighlighter. |
theme | ShikiHighlighterProps["theme"] | { dark: "github-dark-default", light: "github-light-default" } | A single theme name or a { light, dark, ... } map. |
streaming | boolean | false | Skips tokenization and renders the plain code while true. |
delay | number | 150 | Milliseconds useShikiHighlighter waits after code last changed before tokenizing. |
className | string | Merged 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.
Pass streaming yourself, as in the standalone step above; every other prop behaves identically to the runtime lane.