Code block syntax highlighting with react-shiki or react-syntax-highlighter.
function greet(name: string) {
return `Hello, ${name}!`;
}
// Usage
const message = greet("World");assistant-ui provides two options for syntax highlighting:
- react-shiki (recommended for performance & dynamic language support)
- react-syntax-highlighter (legacy - Prism or Highlight.js based)
react-shiki
Add shiki-highlighter
npx shadcn@latest add @assistant-ui/shiki-highlighterThe @assistant-uinamespace resolves the Radix or Base UI flavor from your project's style through the style-aware registry entry in components.json. Without that entry, add by direct URL instead:
npx shadcn@latest add https://r.assistant-ui.com/base/shiki-highlighter.jsonMain Component
npm install @assistant-ui/react @assistant-ui/react-markdown react-shiki"use client";import type { FC } from "react";import { useShikiHighlighter, type ShikiHighlighterProps } from "react-shiki";import { useAui, useAuiState } from "@assistant-ui/react";import type { SyntaxHighlighterProps as AUIProps } from "@assistant-ui/react-markdown";import { cn } from "@/lib/utils";/** * Props for the SyntaxHighlighter component */export type HighlighterProps = Omit< ShikiHighlighterProps, "children" | "theme"> & { theme?: ShikiHighlighterProps["theme"];} & Pick<AUIProps, "language" | "code"> & Partial<Pick<AUIProps, "node" | "components">>;const containerClassName = "aui-shiki-base [&_pre]:border-border/50 [&_pre]:bg-muted/30! [&_.line]:px-0! [&_pre]:overflow-x-auto [&_pre]:rounded-t-none [&_pre]:rounded-b-xl [&_pre]:border [&_pre]:border-t-0 [&_pre]:p-3.5 [&_pre]:text-[13px] [&_pre]:leading-relaxed";const PlainCode: FC<{ code: string }> = ({ code }) => ( <pre> <code>{code}</code> </pre>);const HighlightedCode: FC<{ code: string; language: HighlighterProps["language"]; theme: NonNullable<HighlighterProps["theme"]>; options: Omit<ShikiHighlighterProps, "children" | "language" | "theme">;}> = ({ code, language, theme, options }) => { const highlighted = useShikiHighlighter(code, language, theme, { ...options, defaultColor: "light-dark()", }); return <>{highlighted ?? <PlainCode code={code} />}</>;};/** * SyntaxHighlighter component, using react-shiki * Use it by passing to `defaultComponents` in `markdown-text.tsx` * * Skips tokenization while the message part is streaming and renders the * plain code in the same container, so streaming costs no Shiki work and * settling is a color change rather than a layout shift. * * @example * const defaultComponents = memoizeMarkdownComponents({ * SyntaxHighlighter, * h1: //... * //...other elements... * }); */export const SyntaxHighlighter: FC<HighlighterProps> = ({ code, language, theme = { dark: "github-dark-default", light: "github-light-default" }, className, style, // Inert: useShikiHighlighter output has no default styles or language label. addDefaultStyles: _addDefaultStyles, showLanguage: _showLanguage, delay = 150, // the part settles before smooth streaming finishes draining, so code keeps changing for a few frames node: _node, components: _components, ...options}) => { const aui = useAui(); const hasPart = aui.part.source !== null; const isStreaming = useAuiState( (s) => hasPart && s.part.status.type === "running", ); const trimmed = code.trim(); return ( <div className={cn( containerClassName, isStreaming && "aui-shiki-streaming", className, )} style={style} > {isStreaming ? ( <PlainCode code={trimmed} /> ) : ( <HighlightedCode code={trimmed} language={language} theme={theme} options={{ ...options, delay }} /> )} </div> );};SyntaxHighlighter.displayName = "SyntaxHighlighter";This adds a /components/assistant-ui/shiki-highlighter.tsx file to your project and
installs the react-shiki dependency. The highlighter can be customized by editing
the config in the shiki-highlighter.tsx file. While a message part is still streaming, the component renders the plain code without tokenization and highlights once the part settles, so streaming stays cheap.
Add it to defaultComponents in markdown-text.tsx
import { SyntaxHighlighter } from "./shiki-highlighter";
export const defaultComponents = memoizeMarkdownComponents({
SyntaxHighlighter: SyntaxHighlighter,
h1: /* ... */,
// ...other elements...
});Options
See react-shiki documentation for all available options.
Key options:
theme- Shiki theme or multi-theme object ({ light, dark, ... })language- Language for highlighting (default:"text")defaultColor- Default color mode (string | false, e.g.light-dark())delay- Delay between highlights, useful for streaming (default:0)customLanguages- Custom languages to preload for dynamic supportcodeToHastOptions- All other options accepted by Shiki'scodeToHast
Dual/multi theme support
To use multiple themes, pass a theme object:
<ShikiHighlighter
/* ... */
theme={{
light: "github-light",
dark: "github-dark",
}}
defaultColor="light-dark()"
/* ... */
>Note: The shiki-highlighter component sets defaultColor="light-dark()" automatically.
Only set this manually if using ShikiHighlighter directly.
With defaultColor="light-dark()", theme switching is automatic based on your site's color-scheme.
No custom Shiki CSS overrides are required.
Set color-scheme on your app root:
System-based (follows OS/browser preference):
:root {
color-scheme: light dark;
}Class-based theme switching:
:root {
color-scheme: light;
}
:root.dark {
color-scheme: dark;
}If you need broader support for older browsers, you can still use the manual CSS-variable switching approach from the Shiki dual-theme docs.
For more information:
Bundle Optimization
By default, react-shiki includes the full Shiki bundle, which contains all supported languages and themes.
To reduce bundle size, you can use the web bundle by changing the import to react-shiki/web, to include a smaller bundle of web related languages:
import ShikiHighlighter, { type ShikiHighlighterProps } from "react-shiki/web";Custom Bundles
For strict bundle size control, react-shiki also supports custom bundles created using createHighlighterCore from react-shiki/core (re-exported from Shiki):
import { createHighlighterCore, createOnigurumaEngine } from "react-shiki/core";
// Create the highlighter
// Use dynamic imports to load languages and themes on client on demand
const customHighlighter = await createHighlighterCore({
themes: [import("@shikijs/themes/nord")],
langs: [
import("@shikijs/langs/javascript"),
import("@shikijs/langs/typescript"),
],
engine: createOnigurumaEngine(import("shiki/wasm")),
});
// Then pass it to the highlighter prop
<SyntaxHighlighter
{...props}
language={language}
theme={theme}
highlighter={customHighlighter}
/>;For more information, see react-shiki - bundle options.
react-syntax-highlighter
This option may be removed in a future release. Consider using react-shiki instead.
Add syntax-highlighter
npx shadcn@latest add @assistant-ui/syntax-highlighterThe @assistant-uinamespace resolves the Radix or Base UI flavor from your project's style through the style-aware registry entry in components.json. Without that entry, add by direct URL instead:
npx shadcn@latest add https://r.assistant-ui.com/base/syntax-highlighter.jsonMain Component
npm install @assistant-ui/react-markdown @assistant-ui/react-syntax-highlighter @types/react-syntax-highlighter react-syntax-highlighterimport { PrismAsyncLight } from "react-syntax-highlighter";import { makePrismAsyncLightSyntaxHighlighter } from "@assistant-ui/react-syntax-highlighter";import type { SyntaxHighlighterProps } from "@assistant-ui/react-markdown";import tsx from "react-syntax-highlighter/dist/esm/languages/prism/tsx";import python from "react-syntax-highlighter/dist/esm/languages/prism/python";import { coldarkCold, coldarkDark,} from "react-syntax-highlighter/dist/cjs/styles/prism";PrismAsyncLight.registerLanguage("js", tsx);PrismAsyncLight.registerLanguage("jsx", tsx);PrismAsyncLight.registerLanguage("ts", tsx);PrismAsyncLight.registerLanguage("tsx", tsx);PrismAsyncLight.registerLanguage("python", python);const syntaxHighlighterCustomStyle = { margin: 0, width: "100%", padding: "1.5rem 1rem",};const LightSyntaxHighlighter = makePrismAsyncLightSyntaxHighlighter({ style: coldarkCold, customStyle: syntaxHighlighterCustomStyle, className: "dark:hidden",});const DarkSyntaxHighlighter = makePrismAsyncLightSyntaxHighlighter({ style: coldarkDark, customStyle: syntaxHighlighterCustomStyle, className: "hidden dark:block",});export const SyntaxHighlighter = (props: SyntaxHighlighterProps) => ( <> <LightSyntaxHighlighter {...props} /> <DarkSyntaxHighlighter {...props} /> </>);Adds a /components/assistant-ui/syntax-highlighter.tsx file to your project and installs the react-syntax-highlighter dependency.
Add it to defaultComponents in markdown-text.tsx
import { SyntaxHighlighter } from "./syntax-highlighter";
export const defaultComponents = memoizeMarkdownComponents({
SyntaxHighlighter: SyntaxHighlighter,
h1: /* ... */,
// ...other elements...
});Options
Supports all options from react-syntax-highlighter.
Bundle Optimization
By default, the syntax highlighter uses a light build that only includes languages you register. To include all languages:
import { makePrismAsyncSyntaxHighlighter } from "@assistant-ui/react-syntax-highlighter/full";