Data display
code-block
Same API as upstream: shadcn/ui docs
$
npx shadcn@latest add @preactcn/code-blockPlain
Plain
$ pnpm build
> next build
▲ Next.js 16.0.0
Creating an optimized production build ...
✓ Compiled successfully in 4.2s
✓ Generating static pages (12/12)
Route (app) Size First Load JS
┌ ○ / 1.9 kB 112 kB
├ ○ /_not-found 993 B 103 kB
└ ƒ /api/chat 141 B 102 kBCodeBlockPlain
function CodeBlockPlain() {
return (
<Example title="Plain">
<CodeBlock>
<CodeBlockContent>{build}</CodeBlockContent>
</CodeBlock>
</Example>
)
}Wrap
Wrap
TypeError: Cannot read properties of undefined (reading 'map')
at MessageList (components/message-list.tsx:18:22)
at renderWithHooks (react-dom.development.js:16305:18)
at mountIndeterminateComponent (react-dom.development.js:20069:13)CodeBlockWrap
function CodeBlockWrap() {
return (
<Example title="Wrap">
<CodeBlock>
<CodeBlockContent wrap>{error}</CodeBlockContent>
</CodeBlock>
</Example>
)
}Line Numbers
Line Numbers
import { streamText } from "ai"
export async function POST(request: Request) { const { messages } = await request.json()
const result = streamText({ model: "anthropic/claude-sonnet-4.5", messages, })
return result.toUIMessageStreamResponse()}CodeBlockLineNumbers
function CodeBlockLineNumbers() {
const lines = useHighlightedCode(source, "ts")
return (
<Example title="Line Numbers">
<CodeBlock>
<CodeBlockContent lineNumbers>
{lines.map((line, index) => (
<CodeBlockLine key={index}>{line}</CodeBlockLine>
))}
</CodeBlockContent>
</CodeBlock>
</Example>
)
}Header
Header
Terminal
$ pnpm add ai @ai-sdk/react zod
Progress: resolved 412, reused 398, downloaded 14, added 14, done
dependencies:
+ @ai-sdk/react 3.0.12
+ ai 6.0.12
+ zod 4.1.12
Done in 2.8s using pnpm v10.18.0CodeBlockWithHeader
function CodeBlockWithHeader() {
return (
<Example title="Header">
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>
<TerminalIcon />
Terminal
</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={install} />
</CodeBlockActions>
</CodeBlockHeader>
<CodeBlockContent>{install}</CodeBlockContent>
</CodeBlock>
</Example>
)
}Actions
Actions
app/api/chat/route.ts
import { streamText } from "ai"
export async function POST(request: Request) { const { messages } = await request.json()
const result = streamText({ model: "anthropic/claude-sonnet-4.5", messages, })
return result.toUIMessageStreamResponse()}CodeBlockWithActions
function CodeBlockWithActions() {
const [wrap, setWrap] = useState(false)
const lines = useHighlightedCode(source, "ts")
return (
<Example title="Actions">
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>app/api/chat/route.ts</CodeBlockTitle>
<CodeBlockActions>
<Button
variant="ghost"
size="icon-xs"
aria-label="Wrap lines"
aria-pressed={wrap}
className="aria-pressed:text-foreground"
onClick={() => setWrap(!wrap)}
>
<WrapTextIcon />
</Button>
<Button
variant="ghost"
size="icon-xs"
aria-label="Download"
onClick={() => download(source, "route.ts")}
>
<DownloadIcon />
</Button>
<CodeBlockCopyButton value={source} />
</CodeBlockActions>
</CodeBlockHeader>
<CodeBlockContent wrap={wrap} lineNumbers>
{lines.map((line, index) => (
<CodeBlockLine key={index}>{line}</CodeBlockLine>
))}
</CodeBlockContent>
</CodeBlock>
</Example>
)
}Destructive
Destructive
Error
TypeError: Cannot read properties of undefined (reading 'map')
at MessageList (components/message-list.tsx:18:22)
at renderWithHooks (react-dom.development.js:16305:18)
at mountIndeterminateComponent (react-dom.development.js:20069:13)CodeBlockDestructive
function CodeBlockDestructive() {
return (
<Example title="Destructive">
<CodeBlock variant="destructive">
<CodeBlockHeader>
<CodeBlockTitle>Error</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={error} />
</CodeBlockActions>
</CodeBlockHeader>
<CodeBlockContent wrap>{error}</CodeBlockContent>
</CodeBlock>
</Example>
)
}Diff
Diff
components/greeting.tsx
export function Greeting({ name }: Props) { return <p>Hello, {name}</p> return <p className="text-sm">Hello, {name}!</p>}CodeBlockDiff
function CodeBlockDiff() {
const lines = useHighlightedCode(
diff.map((line) => line.text).join("\n"),
"tsx"
)
return (
<Example title="Diff">
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>components/greeting.tsx</CodeBlockTitle>
</CodeBlockHeader>
<CodeBlockContent lineNumbers>
{lines.map((line, index) => (
<CodeBlockLine key={index} variant={diff[index]?.variant}>
{line}
</CodeBlockLine>
))}
</CodeBlockContent>
</CodeBlock>
</Example>
)
}Highlighted Lines
Highlighted Lines
import { streamText } from "ai"
export async function POST(request: Request) { const { messages } = await request.json()
const result = streamText({ model: "anthropic/claude-sonnet-4.5", messages, })
return result.toUIMessageStreamResponse()}CodeBlockHighlightedLines
function CodeBlockHighlightedLines() {
const lines = useHighlightedCode(source, "ts")
return (
<Example title="Highlighted Lines">
<CodeBlock>
<CodeBlockContent lineNumbers>
{lines.map((line, index) => (
<CodeBlockLine
key={index}
variant={index >= 5 && index <= 8 ? "highlighted" : "default"}
>
{line}
</CodeBlockLine>
))}
</CodeBlockContent>
</CodeBlock>
</Example>
)
}Height
Height
Logs
[01] GET /api/chat 200 in 12ms
[02] GET /api/chat 200 in 19ms
[03] GET /api/chat 200 in 26ms
[04] GET /api/chat 200 in 33ms
[05] GET /api/chat 200 in 40ms
[06] GET /api/chat 200 in 47ms
[07] GET /api/chat 200 in 14ms
[08] GET /api/chat 200 in 21ms
[09] GET /api/chat 200 in 28ms
[10] GET /api/chat 200 in 35ms
[11] GET /api/chat 200 in 42ms
[12] GET /api/chat 200 in 49ms
[13] GET /api/chat 200 in 16ms
[14] GET /api/chat 200 in 23ms
[15] GET /api/chat 200 in 30ms
[16] GET /api/chat 200 in 37ms
[17] GET /api/chat 200 in 44ms
[18] GET /api/chat 200 in 51ms
[19] GET /api/chat 200 in 18ms
[20] GET /api/chat 200 in 25ms
[21] GET /api/chat 200 in 32ms
[22] GET /api/chat 200 in 39ms
[23] GET /api/chat 200 in 46ms
[24] GET /api/chat 200 in 13ms
[25] GET /api/chat 200 in 20ms
[26] GET /api/chat 200 in 27ms
[27] GET /api/chat 200 in 34ms
[28] GET /api/chat 200 in 41ms
[29] GET /api/chat 200 in 48ms
[30] GET /api/chat 200 in 15ms
[31] GET /api/chat 200 in 22ms
[32] GET /api/chat 200 in 29ms
[33] GET /api/chat 200 in 36ms
[34] GET /api/chat 200 in 43ms
[35] GET /api/chat 200 in 50ms
[36] GET /api/chat 200 in 17ms
[37] GET /api/chat 200 in 24ms
[38] GET /api/chat 200 in 31ms
[39] GET /api/chat 200 in 38ms
[40] GET /api/chat 200 in 45msCodeBlockHeight
function CodeBlockHeight() {
return (
<Example title="Height">
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>Logs</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={log} />
</CodeBlockActions>
</CodeBlockHeader>
<CodeBlockContent className="max-h-48 scroll-fade-y">
{log}
</CodeBlockContent>
</CodeBlock>
</Example>
)
}Show More
Show More
components/chat.tsx
"use client"
import * as React from "react"import { useChat } from "@ai-sdk/react"
import { Button } from "@/components/ui/button"import { Input } from "@/components/ui/input"
export function Chat() { const [input, setInput] = React.useState("") const { messages, sendMessage, status } = useChat()
return ( <div className="flex flex-col gap-4"> {messages.map((message) => ( <div key={message.id}> {message.parts.map((part, index) => part.type === "text" ? <p key={index}>{part.text}</p> : null )} </div> ))} <form onSubmit={(event) => { event.preventDefault() sendMessage({ text: input }) setInput("") }} > <Input value={input} onChange={(event) => setInput(event.target.value)} placeholder="Ask anything" /> <Button disabled={status !== "ready"}>Send</Button> </form> </div> )}CodeBlockShowMore
function CodeBlockShowMore() {
const [expanded, setExpanded] = useState(false)
const lines = useHighlightedCode(chat, "tsx")
const id = useId()
return (
<Example title="Show More">
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>components/chat.tsx</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={chat} />
</CodeBlockActions>
</CodeBlockHeader>
<CodeBlockContent
id={id}
lineNumbers
className={
expanded ? undefined : "max-h-64 scroll-fade-y overflow-y-hidden"
}
>
{lines.map((line, index) => (
<CodeBlockLine key={index}>{line}</CodeBlockLine>
))}
</CodeBlockContent>
<CodeBlockFooter className="justify-center">
<Button
variant="ghost"
size="xs"
aria-expanded={expanded}
aria-controls={id}
onClick={() => setExpanded(!expanded)}
>
<ChevronDownIcon
className="transition-transform in-aria-expanded:rotate-180 aria-expanded:rotate-180"
/>
{expanded ? "Show less" : "Show more"}
</Button>
</CodeBlockFooter>
</CodeBlock>
</Example>
)
}Show More with Max Height
Show More with Max Height
components/chat.tsx
"use client"
import * as React from "react"import { useChat } from "@ai-sdk/react"
import { Button } from "@/components/ui/button"import { Input } from "@/components/ui/input"
export function Chat() { const [input, setInput] = React.useState("") const { messages, sendMessage, status } = useChat()
return ( <div className="flex flex-col gap-4"> {messages.map((message) => ( <div key={message.id}> {message.parts.map((part, index) => part.type === "text" ? <p key={index}>{part.text}</p> : null )} </div> ))} <form onSubmit={(event) => { event.preventDefault() sendMessage({ text: input }) setInput("") }} > <Input value={input} onChange={(event) => setInput(event.target.value)} placeholder="Ask anything" /> <Button disabled={status !== "ready"}>Send</Button> </form> </div> )}CodeBlockShowMoreScroll
function CodeBlockShowMoreScroll() {
const [expanded, setExpanded] = useState(false)
const lines = useHighlightedCode(chat, "tsx")
const id = useId()
return (
<Example title="Show More with Max Height">
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>components/chat.tsx</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={chat} />
</CodeBlockActions>
</CodeBlockHeader>
<CodeBlockContent
id={id}
lineNumbers
className={
expanded
? "max-h-96 scroll-fade-y"
: "max-h-64 scroll-fade-y overflow-y-hidden"
}
>
{lines.map((line, index) => (
<CodeBlockLine key={index}>{line}</CodeBlockLine>
))}
</CodeBlockContent>
<CodeBlockFooter className="justify-center">
<Button
variant="ghost"
size="xs"
aria-expanded={expanded}
aria-controls={id}
onClick={() => setExpanded(!expanded)}
>
<ChevronDownIcon
className="transition-transform in-aria-expanded:rotate-180 aria-expanded:rotate-180"
/>
{expanded ? "Show less" : "Show more"}
</Button>
</CodeBlockFooter>
</CodeBlock>
</Example>
)
}Syntax Highlighting
Syntax Highlighting
app/api/chat/route.ts
import { streamText } from "ai"
export async function POST(request: Request) { const { messages } = await request.json()
const result = streamText({ model: "anthropic/claude-sonnet-4.5", messages, })
return result.toUIMessageStreamResponse()}CodeBlockHighlighted
function CodeBlockHighlighted() {
const lines = useHighlightedCode(source, "ts")
return (
<Example title="Syntax Highlighting">
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>
<FileCodeIcon />
app/api/chat/route.ts
</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={source} />
</CodeBlockActions>
</CodeBlockHeader>
<CodeBlockContent lineNumbers>
{lines.map((line, index) => (
<CodeBlockLine key={index}>{line}</CodeBlockLine>
))}
</CodeBlockContent>
</CodeBlock>
</Example>
)
}Empty
Empty
CodeBlockEmpty
function CodeBlockEmpty() {
return (
<Example title="Empty">
<CodeBlock>
<CodeBlockContent>{""}</CodeBlockContent>
</CodeBlock>
</Example>
)
}Small
Small
pnpm add ai @ai-sdk/react zod
$ pnpm add ai @ai-sdk/react zod
Progress: resolved 412, reused 398, downloaded 14, added 14, done
dependencies:
+ @ai-sdk/react 3.0.12
+ ai 6.0.12
+ zod 4.1.12
Done in 2.8s using pnpm v10.18.0CodeBlockSmall
function CodeBlockSmall() {
return (
<Example title="Small">
<CodeBlock size="sm">
<CodeBlockHeader>
<CodeBlockTitle>
<TerminalIcon />
pnpm add ai @ai-sdk/react zod
</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={install} />
</CodeBlockActions>
</CodeBlockHeader>
<CodeBlockContent wrap>{install}</CodeBlockContent>
</CodeBlock>
</Example>
)
}Small Destructive
Small Destructive
TypeError: Cannot read properties of undefined (reading 'map')
at MessageList (components/message-list.tsx:18:22)
at renderWithHooks (react-dom.development.js:16305:18)
at mountIndeterminateComponent (react-dom.development.js:20069:13)CodeBlockSmallDestructive
function CodeBlockSmallDestructive() {
return (
<Example title="Small Destructive">
<CodeBlock size="sm" variant="destructive">
<CodeBlockContent wrap>{error}</CodeBlockContent>
</CodeBlock>
</Example>
)
}Trace
Trace
$ pnpm add ai @ai-sdk/react zod
Progress: resolved 412, reused 398, downloaded 14, added 14, done
dependencies:
+ @ai-sdk/react 3.0.12
+ ai 6.0.12
+ zod 4.1.12
Done in 2.8s using pnpm v10.18.0TypeError: Cannot read properties of undefined (reading 'map')
at MessageList (components/message-list.tsx:18:22)
at renderWithHooks (react-dom.development.js:16305:18)
at mountIndeterminateComponent (react-dom.development.js:20069:13)CodeBlockTrace
function CodeBlockTrace() {
return (
<Example title="Trace">
<Trace>
<TraceCollapsible defaultOpen>
<TraceCollapsibleTrigger>
<TraceLine>
<TraceIcon>
<TerminalIcon />
</TraceIcon>
<TraceTitle>Ran pnpm add ai @ai-sdk/react zod</TraceTitle>
</TraceLine>
</TraceCollapsibleTrigger>
<TraceCollapsibleContent>
<CodeBlock size="sm">
<CodeBlockContent wrap className="max-h-64">
{install}
</CodeBlockContent>
</CodeBlock>
</TraceCollapsibleContent>
</TraceCollapsible>
<TraceCollapsible defaultOpen>
<TraceCollapsibleTrigger>
<TraceLine status="error">
<TraceIcon>
<TerminalIcon />
</TraceIcon>
<TraceTitle>Failed to run pnpm dev</TraceTitle>
</TraceLine>
</TraceCollapsibleTrigger>
<TraceCollapsibleContent>
<CodeBlock size="sm" variant="destructive">
<CodeBlockContent wrap className="max-h-64">
{error}
</CodeBlockContent>
</CodeBlock>
</TraceCollapsibleContent>
</TraceCollapsible>
</Trace>
</Example>
)
}Typeset
Typeset
Stream a response
Create a route handler that calls streamText and returns the stream to the client. The useChat hook reads it on the other side.
app/api/chat/route.ts
import { streamText } from "ai"
export async function POST(request: Request) { const { messages } = await request.json()
const result = streamText({ model: "anthropic/claude-sonnet-4.5", messages, })
return result.toUIMessageStreamResponse()}Then install the packages the route uses.
pnpm add ai @ai-sdk/react zodThe model streams text parts as they arrive, so the answer renders while it is written.
CodeBlockTypeset
function CodeBlockTypeset() {
const lines = useHighlightedCode(source, "ts")
const command = useHighlightedCode("pnpm add ai @ai-sdk/react zod", "bash")
return (
<Example title="Typeset">
<div className="typeset max-w-2xl">
<h3>Stream a response</h3>
<p>
Create a route handler that calls <code>streamText</code> and returns
the stream to the client. The <code>useChat</code> hook reads it on
the other side.
</p>
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>
<FileCodeIcon />
app/api/chat/route.ts
</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={source} />
</CodeBlockActions>
</CodeBlockHeader>
<CodeBlockContent lineNumbers>
{lines.map((line, index) => (
<CodeBlockLine key={index}>{line}</CodeBlockLine>
))}
</CodeBlockContent>
</CodeBlock>
<p>Then install the packages the route uses.</p>
<CodeBlock>
<CodeBlockContent>
{command.map((line, index) => (
<CodeBlockLine key={index}>{line}</CodeBlockLine>
))}
</CodeBlockContent>
</CodeBlock>
<p>
The model streams text parts as they arrive, so the answer renders
while it is written.
</p>
</div>
</Example>
)
}Wrap and Line Numbers
Wrap and Line Numbers
components/chat.tsx
"use client"
import * as React from "react"import { useChat } from "@ai-sdk/react"
import { Button } from "@/components/ui/button"import { Input } from "@/components/ui/input"
export function Chat() { const [input, setInput] = React.useState("") const { messages, sendMessage, status } = useChat()
return ( <div className="flex flex-col gap-4"> {messages.map((message) => ( <div key={message.id}> {message.parts.map((part, index) => part.type === "text" ? <p key={index}>{part.text}</p> : null )} </div> ))} <form onSubmit={(event) => { event.preventDefault() sendMessage({ text: input }) setInput("") }} > <Input value={input} onChange={(event) => setInput(event.target.value)} placeholder="Ask anything" /> <Button disabled={status !== "ready"}>Send</Button> </form> </div> )}CodeBlockWrapLineNumbers
function CodeBlockWrapLineNumbers() {
return (
<Example title="Wrap and Line Numbers">
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>components/chat.tsx</CodeBlockTitle>
</CodeBlockHeader>
<Highlighted code={chat} language="tsx" wrap lineNumbers />
</CodeBlock>
</Example>
)
}Start Line
Start Line
app/dashboard/page.tsx
if (!session) { redirect("/login") }
const projects = await db.project.findMany({ where: { ownerId: session.user.id }, orderBy: { updatedAt: "desc" }, })
return <ProjectList projects={projects} />}CodeBlockStartLine
function CodeBlockStartLine() {
return (
<Example title="Start Line">
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>app/dashboard/page.tsx</CodeBlockTitle>
</CodeBlockHeader>
<Highlighted code={middle} language="tsx" lineNumbers startLine={995} />
</CodeBlock>
</Example>
)
}Diff, Wrap and Highlighting
Diff, Wrap and Highlighting
app/api/projects/route.ts
export async function GET(request: Request) { const { searchParams } = new URL(request.url, "http://localhost:3000/api/projects?include=owner,members") const { searchParams } = new URL(request.url, process.env.NEXT_PUBLIC_APP_URL ?? request.headers.get("origin") ?? undefined) const include = searchParams.get("include")?.split(",") ?? [] return Response.json(await getProjects({ include }))}CodeBlockDiffWrap
function CodeBlockDiffWrap() {
const lines = useHighlightedCode(
longDiff.map((line) => line.text).join("\n"),
"ts"
)
return (
<Example title="Diff, Wrap and Highlighting">
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>app/api/projects/route.ts</CodeBlockTitle>
</CodeBlockHeader>
<CodeBlockContent wrap lineNumbers startLine={12}>
{lines.map((line, index) => (
<CodeBlockLine key={index} variant={longDiff[index]?.variant}>
{line}
</CodeBlockLine>
))}
</CodeBlockContent>
</CodeBlock>
</Example>
)
}Header Edges
Header Edges
Title only
console.log("title, no actions")$ pnpm add ai @ai-sdk/react zod
Progress: resolved 412, reused 398, downloaded 14, added 14, done
dependencies:
+ @ai-sdk/react 3.0.12
+ ai 6.0.12
+ zod 4.1.12
Done in 2.8s using pnpm v10.18.0apps/web/app/(dashboard)/settings/organizations/[organizationId]/members/invitations/page.tsx
export default function Page() {}CodeBlockHeaderEdges
function CodeBlockHeaderEdges() {
return (
<Example title="Header Edges">
<div className="flex flex-col gap-4">
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>Title only</CodeBlockTitle>
</CodeBlockHeader>
<CodeBlockContent>
{'console.log("title, no actions")'}
</CodeBlockContent>
</CodeBlock>
<CodeBlock>
<CodeBlockActions>
<CodeBlockCopyButton value={install} />
</CodeBlockActions>
<CodeBlockContent>{install}</CodeBlockContent>
</CodeBlock>
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>
<FileCodeIcon />
apps/web/app/(dashboard)/settings/organizations/[organizationId]/members/invitations/page.tsx
</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={source} />
</CodeBlockActions>
</CodeBlockHeader>
<CodeBlockContent>
{"export default function Page() {}"}
</CodeBlockContent>
</CodeBlock>
</div>
</Example>
)
}Single Line and Empty
Single Line and Empty
Install
pnpm dlx shadcn@latest add code-blockoutput.log
pnpm devCodeBlockSmallest
function CodeBlockSmallest() {
return (
<Example title="Single Line and Empty">
<div className="flex flex-col gap-4">
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>Install</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value="pnpm dlx shadcn@latest add code-block" />
</CodeBlockActions>
</CodeBlockHeader>
<Highlighted
code="pnpm dlx shadcn@latest add code-block"
language="bash"
/>
</CodeBlock>
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>output.log</CodeBlockTitle>
</CodeBlockHeader>
<CodeBlockContent>{""}</CodeBlockContent>
</CodeBlock>
<CodeBlock>
<Highlighted code="pnpm dev" language="bash" />
</CodeBlock>
</div>
</Example>
)
}Small Combinations
Small Combinations
components/greeting.tsx
export function Greeting({ name }: Props) { return <p>Hello, {name}</p> return <p className="text-sm">Hello, {name}!</p>}import { streamText } from "ai"
export async function POST(request: Request) { const { messages } = await request.json()
const result = streamText({ model: "anthropic/claude-sonnet-4.5", messages, })
return result.toUIMessageStreamResponse()}CodeBlockSmallCombinations
function CodeBlockSmallCombinations() {
const lines = useHighlightedCode(
diff.map((line) => line.text).join("\n"),
"tsx"
)
return (
<Example title="Small Combinations">
<div className="flex flex-col gap-4">
<CodeBlock size="sm">
<CodeBlockHeader>
<CodeBlockTitle>components/greeting.tsx</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={source} />
</CodeBlockActions>
</CodeBlockHeader>
<CodeBlockContent lineNumbers>
{lines.map((line, index) => (
<CodeBlockLine key={index} variant={diff[index]?.variant}>
{line}
</CodeBlockLine>
))}
</CodeBlockContent>
</CodeBlock>
<CodeBlock size="sm">
<Highlighted
code={source}
language="ts"
lineNumbers
startLine={995}
/>
</CodeBlock>
</div>
</Example>
)
}Streaming
Streaming
app/api/chat/route.ts
import { streamText } from "ai"
export async function POST(request: Request) { const { messages } = await request.json()
const result = streamText({ model: "anthropic/claude-sonnet-4.5", messages, })
return result.toUIMessageStreamResponse()}pnpm dlx shadcn@latest add code-blockcomponents/search.tsx
export function Search() { const [query, setQuery] = React.useState("") const results = useSearch(query) const debounced = useDebounce(query, 300) const results = useSearch(debounced)
return <SearchResults results={results} />}CodeBlockStreamed
function CodeBlockStreamed() {
const [position, replay] = useStream(streamedLength)
return (
<Example title="Streaming">
<div className="flex flex-col gap-4">
{streamed.map(
(block, index) =>
position > offsets[index] && (
<StreamedBlock
key={block.code}
block={block}
code={block.code.slice(0, position - offsets[index])}
/>
)
)}
<Button
variant="outline"
size="sm"
className="self-start"
disabled={position < streamedLength}
onClick={replay}
>
Replay
</Button>
</div>
</Example>
)
}Markdown
Markdown
Wrap the input in a useDebounce hook:
tsx
export function useDebounce<T>(value: T, delay = 300) { const [debounced, setDebounced] = React.useState(value)
React.useEffect(() => { const timeout = setTimeout(() => setDebounced(value), delay) return () => clearTimeout(timeout) }, [value, delay])
return debounced}Then install it:
bash
pnpm dlx shadcn@latest add use-debounceCodeBlockMarkdown
function CodeBlockMarkdown() {
const [position, replay] = useStream(answer.length)
return (
<Example title="Markdown">
<div className="flex flex-col gap-4 text-sm">
<Streamdown
components={markdownComponents}
controls={false}
isAnimating={position < answer.length}
>
{answer.slice(0, position)}
</Streamdown>
<Button
variant="outline"
size="sm"
className="self-start"
disabled={position < answer.length}
onClick={replay}
>
Replay
</Button>
</div>
</Example>
)
}Languages
Languages
package.json
{ "name": "acme-chat", "scripts": { "dev": "next dev --turbopack" }, "dependencies": { "ai": "^6.0.12", "zod": "^4.1.12" }}app/globals.css
@import "tailwindcss";
:root { --radius: 0.625rem;}main.py
def greet(name: str) -> str: return f"Hello, {name}!"notes.txt
An unknown language renders as plain lines.ANSI
$ pnpm test[32m✓[39m src/format.test.ts [2m(4 tests)[22m[31m✗[39m src/stream.test.ts [2m(3 tests | 1 failed)[22m
Tests [31m1 failed[39m | [32m6 passed[39m (7)CodeBlockLanguages
function CodeBlockLanguages() {
const lines = useHighlightedCode(ansi, "ansi")
return (
<Example title="Languages">
<div className="flex flex-col gap-4">
{samples.map((sample) => (
<CodeBlock key={sample.language}>
<CodeBlockHeader>
<CodeBlockTitle>{sample.title}</CodeBlockTitle>
</CodeBlockHeader>
<Highlighted code={sample.code} language={sample.language} />
</CodeBlock>
))}
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>ANSI</CodeBlockTitle>
</CodeBlockHeader>
<CodeBlockContent wrap>
{lines.map((line, index) => (
<CodeBlockLine key={index}>{line}</CodeBlockLine>
))}
</CodeBlockContent>
</CodeBlock>
</div>
</Example>
)
}Card and Dialog
Card and Dialog
CodeBlockInCard
function CodeBlockInCard() {
return (
<Example title="Card and Dialog">
<div className="flex flex-col items-start gap-4">
<Card className="w-full">
<CardHeader>
<CardTitle>Install the AI SDK</CardTitle>
<CardDescription>Add the packages to your project.</CardDescription>
</CardHeader>
<CardContent>
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>Terminal</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value="pnpm add ai @ai-sdk/react zod" />
</CodeBlockActions>
</CodeBlockHeader>
<Highlighted
code="pnpm add ai @ai-sdk/react zod"
language="bash"
/>
</CodeBlock>
</CardContent>
</Card>
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">View route</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-xl">
<DialogHeader>
<DialogTitle>app/api/chat/route.ts</DialogTitle>
<DialogDescription>
The route that streams the answer.
</DialogDescription>
</DialogHeader>
<CodeBlock>
<Highlighted code={source} language="ts" lineNumbers />
</CodeBlock>
</DialogContent>
</Dialog>
</div>
</Example>
)
}Chat Answer
Chat Answer
CodeBlockInMessage
function CodeBlockInMessage() {
return (
<Example title="Chat Answer">
<div className="flex flex-col gap-4 text-sm">
<Message align="end">
<MessageContent>
<Bubble>
<BubbleContent>How do I stream a response?</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message>
<MessageContent className="gap-3">
<p>Create a route handler that returns the stream:</p>
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>app/api/chat/route.ts</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={source} />
</CodeBlockActions>
</CodeBlockHeader>
<Highlighted code={source} language="ts" />
</CodeBlock>
<p>The client reads it with useChat.</p>
</MessageContent>
</Message>
</div>
</Example>
)
}Tabs
Tabs
app/api/chat/route.ts
import { streamText } from "ai"
export async function POST(request: Request) { const { messages } = await request.json()
const result = streamText({ model: "anthropic/claude-sonnet-4.5", messages, })
return result.toUIMessageStreamResponse()}CodeBlockTabs
function CodeBlockTabs() {
return (
<Example title="Tabs">
<Tabs defaultValue="route">
<TabsList>
{files.map((file) => (
<TabsTrigger key={file.id} value={file.id}>
{file.title.split("/").at(-1)}
</TabsTrigger>
))}
</TabsList>
{files.map((file) => (
<TabsContent key={file.id} value={file.id}>
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>{file.title}</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={file.code} />
</CodeBlockActions>
</CodeBlockHeader>
<Highlighted
code={file.code}
language={file.language}
lineNumbers
/>
</CodeBlock>
</TabsContent>
))}
</Tabs>
</Example>
)
}RTL
RTL
أنشئ ملف المسار وأعد البث إلى العميل.
app/api/chat/route.ts
import { streamText } from "ai"
export async function POST(request: Request) { const { messages } = await request.json()
const result = streamText({ model: "anthropic/claude-sonnet-4.5", messages, })
return result.toUIMessageStreamResponse()}CodeBlockRtl
function CodeBlockRtl() {
return (
<Example title="RTL">
<div dir="rtl" className="flex flex-col gap-3 text-sm">
<p>أنشئ ملف المسار وأعد البث إلى العميل.</p>
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>app/api/chat/route.ts</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={source} />
</CodeBlockActions>
</CodeBlockHeader>
<Highlighted code={source} language="ts" lineNumbers />
</CodeBlock>
</div>
</Example>
)
}Full example source
code-block-example.tsx
import type { ComponentProps } from "preact"
import { useEffect, useId, useState } from "preact/hooks"
import {
Streamdown,
useIsCodeFenceIncomplete,
type ExtraProps,
} from "streamdown"
import {
Example,
ExampleWrapper,
} from "@/components/example"
import { useHighlightedCode } from "@/hooks/use-highlighted-code"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import {
CodeBlock,
CodeBlockActions,
CodeBlockContent,
CodeBlockCopyButton,
CodeBlockFooter,
CodeBlockHeader,
CodeBlockLine,
CodeBlockTitle,
} from "@/components/ui/code-block"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Message, MessageContent } from "@/components/ui/message"
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/components/ui/tabs"
import {
Trace,
TraceCollapsible,
TraceCollapsibleContent,
TraceCollapsibleTrigger,
TraceIcon,
TraceLine,
TraceTitle,
} from "@/components/ui/trace"
import {
ChevronDownIcon,
DownloadIcon,
FileCodeIcon,
TerminalIcon,
WrapTextIcon,
} from "lucide-preact"
const build = `$ pnpm build
> next build
▲ Next.js 16.0.0
Creating an optimized production build ...
✓ Compiled successfully in 4.2s
✓ Generating static pages (12/12)
Route (app) Size First Load JS
┌ ○ / 1.9 kB 112 kB
├ ○ /_not-found 993 B 103 kB
└ ƒ /api/chat 141 B 102 kB`
const install = `$ pnpm add ai @ai-sdk/react zod
Progress: resolved 412, reused 398, downloaded 14, added 14, done
dependencies:
+ @ai-sdk/react 3.0.12
+ ai 6.0.12
+ zod 4.1.12
Done in 2.8s using pnpm v10.18.0`
const error = `TypeError: Cannot read properties of undefined (reading 'map')
at MessageList (components/message-list.tsx:18:22)
at renderWithHooks (react-dom.development.js:16305:18)
at mountIndeterminateComponent (react-dom.development.js:20069:13)`
const source = `import { streamText } from "ai"
export async function POST(request: Request) {
const { messages } = await request.json()
const result = streamText({
model: "anthropic/claude-sonnet-4.5",
messages,
})
return result.toUIMessageStreamResponse()
}`
const chat = `"use client"
import * as React from "react"
import { useChat } from "@ai-sdk/react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
export function Chat() {
const [input, setInput] = React.useState("")
const { messages, sendMessage, status } = useChat()
return (
<div className="flex flex-col gap-4">
{messages.map((message) => (
<div key={message.id}>
{message.parts.map((part, index) =>
part.type === "text" ? <p key={index}>{part.text}</p> : null
)}
</div>
))}
<form
onSubmit={(event) => {
event.preventDefault()
sendMessage({ text: input })
setInput("")
}}
>
<Input value={input} onChange={(event) => setInput(event.target.value)} placeholder="Ask anything" />
<Button disabled={status !== "ready"}>Send</Button>
</form>
</div>
)
}`
const diff = [
{ variant: "default", text: "export function Greeting({ name }: Props) {" },
{ variant: "removed", text: " return <p>Hello, {name}</p>" },
{
variant: "added",
text: ' return <p className="text-sm">Hello, {name}!</p>',
},
{ variant: "default", text: "}" },
] as const
const log = Array.from(
{ length: 40 },
(_, index) =>
`[${String(index + 1).padStart(2, "0")}] GET /api/chat 200 in ${12 + ((index * 7) % 40)}ms`
).join("\n")
export default function CodeBlockExample() {
return (
<ExampleWrapper className="md:grid-cols-1">
<CodeBlockPlain />
<CodeBlockWrap />
<CodeBlockLineNumbers />
<CodeBlockWithHeader />
<CodeBlockWithActions />
<CodeBlockDestructive />
<CodeBlockDiff />
<CodeBlockHighlightedLines />
<CodeBlockHeight />
<CodeBlockShowMore />
<CodeBlockShowMoreScroll />
<CodeBlockHighlighted />
<CodeBlockEmpty />
<CodeBlockSmall />
<CodeBlockSmallDestructive />
<CodeBlockTrace />
<CodeBlockTypeset />
<CodeBlockWrapLineNumbers />
<CodeBlockStartLine />
<CodeBlockDiffWrap />
<CodeBlockHeaderEdges />
<CodeBlockSmallest />
<CodeBlockSmallCombinations />
<CodeBlockStreamed />
<CodeBlockMarkdown />
<CodeBlockLanguages />
<CodeBlockInCard />
<CodeBlockInMessage />
<CodeBlockTabs />
<CodeBlockRtl />
</ExampleWrapper>
)
}
function CodeBlockPlain() {
return (
<Example title="Plain">
<CodeBlock>
<CodeBlockContent>{build}</CodeBlockContent>
</CodeBlock>
</Example>
)
}
function CodeBlockWrap() {
return (
<Example title="Wrap">
<CodeBlock>
<CodeBlockContent wrap>{error}</CodeBlockContent>
</CodeBlock>
</Example>
)
}
function CodeBlockLineNumbers() {
const lines = useHighlightedCode(source, "ts")
return (
<Example title="Line Numbers">
<CodeBlock>
<CodeBlockContent lineNumbers>
{lines.map((line, index) => (
<CodeBlockLine key={index}>{line}</CodeBlockLine>
))}
</CodeBlockContent>
</CodeBlock>
</Example>
)
}
function CodeBlockWithHeader() {
return (
<Example title="Header">
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>
<TerminalIcon />
Terminal
</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={install} />
</CodeBlockActions>
</CodeBlockHeader>
<CodeBlockContent>{install}</CodeBlockContent>
</CodeBlock>
</Example>
)
}
function CodeBlockDestructive() {
return (
<Example title="Destructive">
<CodeBlock variant="destructive">
<CodeBlockHeader>
<CodeBlockTitle>Error</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={error} />
</CodeBlockActions>
</CodeBlockHeader>
<CodeBlockContent wrap>{error}</CodeBlockContent>
</CodeBlock>
</Example>
)
}
function CodeBlockDiff() {
const lines = useHighlightedCode(
diff.map((line) => line.text).join("\n"),
"tsx"
)
return (
<Example title="Diff">
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>components/greeting.tsx</CodeBlockTitle>
</CodeBlockHeader>
<CodeBlockContent lineNumbers>
{lines.map((line, index) => (
<CodeBlockLine key={index} variant={diff[index]?.variant}>
{line}
</CodeBlockLine>
))}
</CodeBlockContent>
</CodeBlock>
</Example>
)
}
function CodeBlockHighlightedLines() {
const lines = useHighlightedCode(source, "ts")
return (
<Example title="Highlighted Lines">
<CodeBlock>
<CodeBlockContent lineNumbers>
{lines.map((line, index) => (
<CodeBlockLine
key={index}
variant={index >= 5 && index <= 8 ? "highlighted" : "default"}
>
{line}
</CodeBlockLine>
))}
</CodeBlockContent>
</CodeBlock>
</Example>
)
}
function CodeBlockHeight() {
return (
<Example title="Height">
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>Logs</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={log} />
</CodeBlockActions>
</CodeBlockHeader>
<CodeBlockContent className="max-h-48 scroll-fade-y">
{log}
</CodeBlockContent>
</CodeBlock>
</Example>
)
}
function CodeBlockHighlighted() {
const lines = useHighlightedCode(source, "ts")
return (
<Example title="Syntax Highlighting">
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>
<FileCodeIcon />
app/api/chat/route.ts
</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={source} />
</CodeBlockActions>
</CodeBlockHeader>
<CodeBlockContent lineNumbers>
{lines.map((line, index) => (
<CodeBlockLine key={index}>{line}</CodeBlockLine>
))}
</CodeBlockContent>
</CodeBlock>
</Example>
)
}
function CodeBlockEmpty() {
return (
<Example title="Empty">
<CodeBlock>
<CodeBlockContent>{""}</CodeBlockContent>
</CodeBlock>
</Example>
)
}
function CodeBlockSmall() {
return (
<Example title="Small">
<CodeBlock size="sm">
<CodeBlockHeader>
<CodeBlockTitle>
<TerminalIcon />
pnpm add ai @ai-sdk/react zod
</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={install} />
</CodeBlockActions>
</CodeBlockHeader>
<CodeBlockContent wrap>{install}</CodeBlockContent>
</CodeBlock>
</Example>
)
}
function CodeBlockSmallDestructive() {
return (
<Example title="Small Destructive">
<CodeBlock size="sm" variant="destructive">
<CodeBlockContent wrap>{error}</CodeBlockContent>
</CodeBlock>
</Example>
)
}
function CodeBlockTrace() {
return (
<Example title="Trace">
<Trace>
<TraceCollapsible defaultOpen>
<TraceCollapsibleTrigger>
<TraceLine>
<TraceIcon>
<TerminalIcon />
</TraceIcon>
<TraceTitle>Ran pnpm add ai @ai-sdk/react zod</TraceTitle>
</TraceLine>
</TraceCollapsibleTrigger>
<TraceCollapsibleContent>
<CodeBlock size="sm">
<CodeBlockContent wrap className="max-h-64">
{install}
</CodeBlockContent>
</CodeBlock>
</TraceCollapsibleContent>
</TraceCollapsible>
<TraceCollapsible defaultOpen>
<TraceCollapsibleTrigger>
<TraceLine status="error">
<TraceIcon>
<TerminalIcon />
</TraceIcon>
<TraceTitle>Failed to run pnpm dev</TraceTitle>
</TraceLine>
</TraceCollapsibleTrigger>
<TraceCollapsibleContent>
<CodeBlock size="sm" variant="destructive">
<CodeBlockContent wrap className="max-h-64">
{error}
</CodeBlockContent>
</CodeBlock>
</TraceCollapsibleContent>
</TraceCollapsible>
</Trace>
</Example>
)
}
function CodeBlockTypeset() {
const lines = useHighlightedCode(source, "ts")
const command = useHighlightedCode("pnpm add ai @ai-sdk/react zod", "bash")
return (
<Example title="Typeset">
<div className="typeset max-w-2xl">
<h3>Stream a response</h3>
<p>
Create a route handler that calls <code>streamText</code> and returns
the stream to the client. The <code>useChat</code> hook reads it on
the other side.
</p>
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>
<FileCodeIcon />
app/api/chat/route.ts
</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={source} />
</CodeBlockActions>
</CodeBlockHeader>
<CodeBlockContent lineNumbers>
{lines.map((line, index) => (
<CodeBlockLine key={index}>{line}</CodeBlockLine>
))}
</CodeBlockContent>
</CodeBlock>
<p>Then install the packages the route uses.</p>
<CodeBlock>
<CodeBlockContent>
{command.map((line, index) => (
<CodeBlockLine key={index}>{line}</CodeBlockLine>
))}
</CodeBlockContent>
</CodeBlock>
<p>
The model streams text parts as they arrive, so the answer renders
while it is written.
</p>
</div>
</Example>
)
}
function CodeBlockWithActions() {
const [wrap, setWrap] = useState(false)
const lines = useHighlightedCode(source, "ts")
return (
<Example title="Actions">
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>app/api/chat/route.ts</CodeBlockTitle>
<CodeBlockActions>
<Button
variant="ghost"
size="icon-xs"
aria-label="Wrap lines"
aria-pressed={wrap}
className="aria-pressed:text-foreground"
onClick={() => setWrap(!wrap)}
>
<WrapTextIcon />
</Button>
<Button
variant="ghost"
size="icon-xs"
aria-label="Download"
onClick={() => download(source, "route.ts")}
>
<DownloadIcon />
</Button>
<CodeBlockCopyButton value={source} />
</CodeBlockActions>
</CodeBlockHeader>
<CodeBlockContent wrap={wrap} lineNumbers>
{lines.map((line, index) => (
<CodeBlockLine key={index}>{line}</CodeBlockLine>
))}
</CodeBlockContent>
</CodeBlock>
</Example>
)
}
function download(code: string, filename: string) {
const url = URL.createObjectURL(new Blob([code], { type: "text/plain" }))
const link = document.createElement("a")
link.href = url
link.download = filename
link.click()
URL.revokeObjectURL(url)
}
function CodeBlockShowMore() {
const [expanded, setExpanded] = useState(false)
const lines = useHighlightedCode(chat, "tsx")
const id = useId()
return (
<Example title="Show More">
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>components/chat.tsx</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={chat} />
</CodeBlockActions>
</CodeBlockHeader>
<CodeBlockContent
id={id}
lineNumbers
className={
expanded ? undefined : "max-h-64 scroll-fade-y overflow-y-hidden"
}
>
{lines.map((line, index) => (
<CodeBlockLine key={index}>{line}</CodeBlockLine>
))}
</CodeBlockContent>
<CodeBlockFooter className="justify-center">
<Button
variant="ghost"
size="xs"
aria-expanded={expanded}
aria-controls={id}
onClick={() => setExpanded(!expanded)}
>
<ChevronDownIcon
className="transition-transform in-aria-expanded:rotate-180 aria-expanded:rotate-180"
/>
{expanded ? "Show less" : "Show more"}
</Button>
</CodeBlockFooter>
</CodeBlock>
</Example>
)
}
function CodeBlockShowMoreScroll() {
const [expanded, setExpanded] = useState(false)
const lines = useHighlightedCode(chat, "tsx")
const id = useId()
return (
<Example title="Show More with Max Height">
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>components/chat.tsx</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={chat} />
</CodeBlockActions>
</CodeBlockHeader>
<CodeBlockContent
id={id}
lineNumbers
className={
expanded
? "max-h-96 scroll-fade-y"
: "max-h-64 scroll-fade-y overflow-y-hidden"
}
>
{lines.map((line, index) => (
<CodeBlockLine key={index}>{line}</CodeBlockLine>
))}
</CodeBlockContent>
<CodeBlockFooter className="justify-center">
<Button
variant="ghost"
size="xs"
aria-expanded={expanded}
aria-controls={id}
onClick={() => setExpanded(!expanded)}
>
<ChevronDownIcon
className="transition-transform in-aria-expanded:rotate-180 aria-expanded:rotate-180"
/>
{expanded ? "Show less" : "Show more"}
</Button>
</CodeBlockFooter>
</CodeBlock>
</Example>
)
}
const middle = ` if (!session) {
redirect("/login")
}
const projects = await db.project.findMany({
where: { ownerId: session.user.id },
orderBy: { updatedAt: "desc" },
})
return <ProjectList projects={projects} />
}`
const longDiff = [
{ variant: "default", text: "export async function GET(request: Request) {" },
{
variant: "removed",
text: ' const { searchParams } = new URL(request.url, "http://localhost:3000/api/projects?include=owner,members")',
},
{
variant: "added",
text: ' const { searchParams } = new URL(request.url, process.env.NEXT_PUBLIC_APP_URL ?? request.headers.get("origin") ?? undefined)',
},
{
variant: "added",
text: ' const include = searchParams.get("include")?.split(",") ?? []',
},
{
variant: "default",
text: " return Response.json(await getProjects({ include }))",
},
{ variant: "default", text: "}" },
] as const
const samples = [
{
language: "json",
title: "package.json",
code: `{
"name": "acme-chat",
"scripts": { "dev": "next dev --turbopack" },
"dependencies": { "ai": "^6.0.12", "zod": "^4.1.12" }
}`,
},
{
language: "css",
title: "app/globals.css",
code: `@import "tailwindcss";
:root {
--radius: 0.625rem;
}`,
},
{
language: "python",
title: "main.py",
code: `def greet(name: str) -> str:
return f"Hello, {name}!"`,
},
{
language: "text",
title: "notes.txt",
code: "An unknown language renders as plain lines.",
},
]
const ansi = `$ pnpm test
\x1b[32m✓\x1b[39m src/format.test.ts \x1b[2m(4 tests)\x1b[22m
\x1b[31m✗\x1b[39m src/stream.test.ts \x1b[2m(3 tests | 1 failed)\x1b[22m
Tests \x1b[31m1 failed\x1b[39m | \x1b[32m6 passed\x1b[39m (7)`
const answer = `Wrap the input in a \`useDebounce\` hook:
\`\`\`tsx
export function useDebounce<T>(value: T, delay = 300) {
const [debounced, setDebounced] = React.useState(value)
React.useEffect(() => {
const timeout = setTimeout(() => setDebounced(value), delay)
return () => clearTimeout(timeout)
}, [value, delay])
return debounced
}
\`\`\`
Then install it:
\`\`\`bash
pnpm dlx shadcn@latest add use-debounce
\`\`\``
const files = [
{ id: "route", title: "app/api/chat/route.ts", language: "ts", code: source },
{ id: "chat", title: "components/chat.tsx", language: "tsx", code: chat },
{
id: "env",
title: ".env.local",
language: "bash",
code: "AI_GATEWAY_API_KEY=your-api-key",
},
]
function Highlighted({
code,
language,
...props
}: ComponentProps<typeof CodeBlockContent> & {
code: string
language: string
}) {
const lines = useHighlightedCode(code, language)
return (
<CodeBlockContent {...props}>
{lines.map((line, index) => (
<CodeBlockLine key={index}>{line}</CodeBlockLine>
))}
</CodeBlockContent>
)
}
function CodeBlockWrapLineNumbers() {
return (
<Example title="Wrap and Line Numbers">
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>components/chat.tsx</CodeBlockTitle>
</CodeBlockHeader>
<Highlighted code={chat} language="tsx" wrap lineNumbers />
</CodeBlock>
</Example>
)
}
function CodeBlockStartLine() {
return (
<Example title="Start Line">
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>app/dashboard/page.tsx</CodeBlockTitle>
</CodeBlockHeader>
<Highlighted code={middle} language="tsx" lineNumbers startLine={995} />
</CodeBlock>
</Example>
)
}
function CodeBlockDiffWrap() {
const lines = useHighlightedCode(
longDiff.map((line) => line.text).join("\n"),
"ts"
)
return (
<Example title="Diff, Wrap and Highlighting">
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>app/api/projects/route.ts</CodeBlockTitle>
</CodeBlockHeader>
<CodeBlockContent wrap lineNumbers startLine={12}>
{lines.map((line, index) => (
<CodeBlockLine key={index} variant={longDiff[index]?.variant}>
{line}
</CodeBlockLine>
))}
</CodeBlockContent>
</CodeBlock>
</Example>
)
}
function CodeBlockHeaderEdges() {
return (
<Example title="Header Edges">
<div className="flex flex-col gap-4">
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>Title only</CodeBlockTitle>
</CodeBlockHeader>
<CodeBlockContent>
{'console.log("title, no actions")'}
</CodeBlockContent>
</CodeBlock>
<CodeBlock>
<CodeBlockActions>
<CodeBlockCopyButton value={install} />
</CodeBlockActions>
<CodeBlockContent>{install}</CodeBlockContent>
</CodeBlock>
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>
<FileCodeIcon />
apps/web/app/(dashboard)/settings/organizations/[organizationId]/members/invitations/page.tsx
</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={source} />
</CodeBlockActions>
</CodeBlockHeader>
<CodeBlockContent>
{"export default function Page() {}"}
</CodeBlockContent>
</CodeBlock>
</div>
</Example>
)
}
function CodeBlockSmallest() {
return (
<Example title="Single Line and Empty">
<div className="flex flex-col gap-4">
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>Install</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value="pnpm dlx shadcn@latest add code-block" />
</CodeBlockActions>
</CodeBlockHeader>
<Highlighted
code="pnpm dlx shadcn@latest add code-block"
language="bash"
/>
</CodeBlock>
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>output.log</CodeBlockTitle>
</CodeBlockHeader>
<CodeBlockContent>{""}</CodeBlockContent>
</CodeBlock>
<CodeBlock>
<Highlighted code="pnpm dev" language="bash" />
</CodeBlock>
</div>
</Example>
)
}
function CodeBlockSmallCombinations() {
const lines = useHighlightedCode(
diff.map((line) => line.text).join("\n"),
"tsx"
)
return (
<Example title="Small Combinations">
<div className="flex flex-col gap-4">
<CodeBlock size="sm">
<CodeBlockHeader>
<CodeBlockTitle>components/greeting.tsx</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={source} />
</CodeBlockActions>
</CodeBlockHeader>
<CodeBlockContent lineNumbers>
{lines.map((line, index) => (
<CodeBlockLine key={index} variant={diff[index]?.variant}>
{line}
</CodeBlockLine>
))}
</CodeBlockContent>
</CodeBlock>
<CodeBlock size="sm">
<Highlighted
code={source}
language="ts"
lineNumbers
startLine={995}
/>
</CodeBlock>
</div>
</Example>
)
}
const streamed: {
title?: string
language: string
code: string
lineNumbers?: boolean
highlight?: number[]
diff?: boolean
}[] = [
{
title: "app/api/chat/route.ts",
language: "ts",
lineNumbers: true,
highlight: [5, 6, 7, 8],
code: source,
},
{ language: "bash", code: "pnpm dlx shadcn@latest add code-block" },
{
title: "components/search.tsx",
language: "tsx",
lineNumbers: true,
diff: true,
code: ` export function Search() {
const [query, setQuery] = React.useState("")
- const results = useSearch(query)
+ const debounced = useDebounce(query, 300)
+ const results = useSearch(debounced)
return <SearchResults results={results} />
}`,
},
]
const offsets = streamed.map((_, index) =>
streamed
.slice(0, index)
.reduce((total, block) => total + block.code.length, 0)
)
const streamedLength = streamed.reduce(
(sum, block) => sum + block.code.length,
0
)
function StreamedBlock({
block,
code,
}: {
block: (typeof streamed)[number]
code: string
}) {
const rows = code.split("\n")
const text = block.diff ? rows.map((row) => row.slice(1)).join("\n") : code
const lines = useHighlightedCode(text, block.language)
return (
<CodeBlock>
{block.title && (
<CodeBlockHeader>
<CodeBlockTitle>{block.title}</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton
value={text}
disabled={code.length < block.code.length}
/>
</CodeBlockActions>
</CodeBlockHeader>
)}
<CodeBlockContent lineNumbers={block.lineNumbers}>
{lines.map((line, index) => (
<CodeBlockLine
key={index}
variant={
block.diff
? rows[index]?.startsWith("+")
? "added"
: rows[index]?.startsWith("-")
? "removed"
: "default"
: block.highlight?.includes(index)
? "highlighted"
: "default"
}
>
{line}
</CodeBlockLine>
))}
</CodeBlockContent>
</CodeBlock>
)
}
function useStream(length: number) {
const [position, setPosition] = useState(length)
useEffect(() => {
if (position >= length) {
return
}
const timeout = setTimeout(() => setPosition(position + 3), 16)
return () => clearTimeout(timeout)
}, [position, length])
return [position, () => setPosition(0)] as const
}
function CodeBlockStreamed() {
const [position, replay] = useStream(streamedLength)
return (
<Example title="Streaming">
<div className="flex flex-col gap-4">
{streamed.map(
(block, index) =>
position > offsets[index] && (
<StreamedBlock
key={block.code}
block={block}
code={block.code.slice(0, position - offsets[index])}
/>
)
)}
<Button
variant="outline"
size="sm"
className="self-start"
disabled={position < streamedLength}
onClick={replay}
>
Replay
</Button>
</div>
</Example>
)
}
function MarkdownCode({
node,
className,
children,
...props
}: ComponentProps<"code"> & ExtraProps) {
const incomplete = useIsCodeFenceIncomplete()
const block = "data-block" in props
const code = typeof children === "string" ? children : ""
const language = /language-(\S+)/.exec((className as string | undefined) ?? "")?.[1] ?? ""
const lines = useHighlightedCode(block ? code : "", language)
if (!block) {
return (
<code className="rounded-sm bg-muted px-1 py-0.5 font-mono text-[0.9em]">
{children}
</code>
)
}
return (
<CodeBlock className="my-3">
<CodeBlockHeader>
<CodeBlockTitle>{language || "text"}</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={code} disabled={incomplete} />
</CodeBlockActions>
</CodeBlockHeader>
<CodeBlockContent>
{lines.map((line, index) => (
<CodeBlockLine key={index}>{line}</CodeBlockLine>
))}
</CodeBlockContent>
</CodeBlock>
)
}
const markdownComponents = { code: MarkdownCode }
function CodeBlockMarkdown() {
const [position, replay] = useStream(answer.length)
return (
<Example title="Markdown">
<div className="flex flex-col gap-4 text-sm">
<Streamdown
components={markdownComponents}
controls={false}
isAnimating={position < answer.length}
>
{answer.slice(0, position)}
</Streamdown>
<Button
variant="outline"
size="sm"
className="self-start"
disabled={position < answer.length}
onClick={replay}
>
Replay
</Button>
</div>
</Example>
)
}
function CodeBlockLanguages() {
const lines = useHighlightedCode(ansi, "ansi")
return (
<Example title="Languages">
<div className="flex flex-col gap-4">
{samples.map((sample) => (
<CodeBlock key={sample.language}>
<CodeBlockHeader>
<CodeBlockTitle>{sample.title}</CodeBlockTitle>
</CodeBlockHeader>
<Highlighted code={sample.code} language={sample.language} />
</CodeBlock>
))}
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>ANSI</CodeBlockTitle>
</CodeBlockHeader>
<CodeBlockContent wrap>
{lines.map((line, index) => (
<CodeBlockLine key={index}>{line}</CodeBlockLine>
))}
</CodeBlockContent>
</CodeBlock>
</div>
</Example>
)
}
function CodeBlockInCard() {
return (
<Example title="Card and Dialog">
<div className="flex flex-col items-start gap-4">
<Card className="w-full">
<CardHeader>
<CardTitle>Install the AI SDK</CardTitle>
<CardDescription>Add the packages to your project.</CardDescription>
</CardHeader>
<CardContent>
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>Terminal</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value="pnpm add ai @ai-sdk/react zod" />
</CodeBlockActions>
</CodeBlockHeader>
<Highlighted
code="pnpm add ai @ai-sdk/react zod"
language="bash"
/>
</CodeBlock>
</CardContent>
</Card>
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">View route</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-xl">
<DialogHeader>
<DialogTitle>app/api/chat/route.ts</DialogTitle>
<DialogDescription>
The route that streams the answer.
</DialogDescription>
</DialogHeader>
<CodeBlock>
<Highlighted code={source} language="ts" lineNumbers />
</CodeBlock>
</DialogContent>
</Dialog>
</div>
</Example>
)
}
function CodeBlockInMessage() {
return (
<Example title="Chat Answer">
<div className="flex flex-col gap-4 text-sm">
<Message align="end">
<MessageContent>
<Bubble>
<BubbleContent>How do I stream a response?</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message>
<MessageContent className="gap-3">
<p>Create a route handler that returns the stream:</p>
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>app/api/chat/route.ts</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={source} />
</CodeBlockActions>
</CodeBlockHeader>
<Highlighted code={source} language="ts" />
</CodeBlock>
<p>The client reads it with useChat.</p>
</MessageContent>
</Message>
</div>
</Example>
)
}
function CodeBlockTabs() {
return (
<Example title="Tabs">
<Tabs defaultValue="route">
<TabsList>
{files.map((file) => (
<TabsTrigger key={file.id} value={file.id}>
{file.title.split("/").at(-1)}
</TabsTrigger>
))}
</TabsList>
{files.map((file) => (
<TabsContent key={file.id} value={file.id}>
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>{file.title}</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={file.code} />
</CodeBlockActions>
</CodeBlockHeader>
<Highlighted
code={file.code}
language={file.language}
lineNumbers
/>
</CodeBlock>
</TabsContent>
))}
</Tabs>
</Example>
)
}
function CodeBlockRtl() {
return (
<Example title="RTL">
<div dir="rtl" className="flex flex-col gap-3 text-sm">
<p>أنشئ ملف المسار وأعد البث إلى العميل.</p>
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>app/api/chat/route.ts</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={source} />
</CodeBlockActions>
</CodeBlockHeader>
<Highlighted code={source} language="ts" lineNumbers />
</CodeBlock>
</div>
</Example>
)
}