Code Block
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 kBWrap
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)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()}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.0Actions
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()}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)Diff
components/greeting.tsx
export function Greeting({ name }: Props) { return <p>Hello, {name}</p> return <p className="text-sm">Hello, {name}!</p>}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()}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 45msShow 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> )}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> )}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()}Empty
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.0Small 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)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)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.
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> )}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} />}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 }))}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() {}Single Line and Empty
Install
pnpm dlx shadcn@latest add code-blockoutput.log
pnpm devSmall 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()}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} />}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-debounceLanguages
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)Card and Dialog
Chat Answer
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()}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()}Official documentation: ui.shadcn.com
Installation
npx shadcn@latest add @l8/code-blockSource
"use client"
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
import { Button } from "@/components/ui/button"
import { CheckIcon, CopyIcon } from "lucide-react"
const codeBlockVariants = cva(
"group/code-block relative flex w-full min-w-0 flex-col overflow-hidden rounded-lg text-sm has-[>[data-slot=code-block-content]:focus-visible]:ring-3 has-[>[data-slot=code-block-content]:focus-visible]:ring-ring/50 data-[size=sm]:text-xs",
{
variants: {
variant: {
default:
"border border-input text-foreground has-[>[data-slot=code-block-content]:focus-visible]:border-ring dark:bg-input/30",
destructive:
"border border-destructive/30 text-destructive dark:border-destructive/40",
},
},
defaultVariants: {
variant: "default",
},
}
)
function CodeBlock({
className,
variant = "default",
size = "default",
...props
}: React.ComponentProps<"div"> &
VariantProps<typeof codeBlockVariants> & { size?: "default" | "sm" }) {
return (
<div
data-slot="code-block"
data-variant={variant}
data-size={size}
className={cn(codeBlockVariants({ variant }), className)}
{...props}
/>
)
}
function CodeBlockHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="code-block-header"
className={cn(
"flex min-w-0 shrink-0 items-center gap-2 px-3 pt-3 text-muted-foreground group-data-[size=sm]/code-block:px-2.5 group-data-[size=sm]/code-block:pt-2.5 group-data-[variant=destructive]/code-block:text-destructive *:has-[>[data-slot=code-block-title]]:min-w-0",
className
)}
{...props}
/>
)
}
function CodeBlockTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="code-block-title"
className={cn(
"min-w-0 truncate font-sans text-sm font-medium text-foreground group-data-[size=sm]/code-block:text-xs group-data-[variant=destructive]/code-block:text-destructive [&_svg]:pointer-events-none [&_svg]:inline-block [&_svg]:align-middle [&_svg:not([class*='size-'])]:size-3.5 *:[svg]:me-1.5",
className
)}
{...props}
/>
)
}
function CodeBlockActions({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="code-block-actions"
className={cn(
"-my-1 ms-auto -me-1.5 flex shrink-0 items-center gap-1 [[data-slot=code-block]>&]:absolute [[data-slot=code-block]>&]:end-2 [[data-slot=code-block]>&]:top-2 [[data-slot=code-block]>&]:z-10 [[data-slot=code-block]>&]:m-0 [[data-slot=code-block]>&]:opacity-0 [[data-slot=code-block]>&]:transition-opacity [[data-slot=code-block]>&]:group-focus-within/code-block:opacity-100 [[data-slot=code-block]>&]:group-hover/code-block:opacity-100 group-data-[size=sm]/code-block:[[data-slot=code-block]>&]:end-1.5 group-data-[size=sm]/code-block:[[data-slot=code-block]>&]:top-1.5 [[data-slot=code-block]>&]:pointer-coarse:opacity-100 [[data-slot=code-block]>&]:*:[button]:bg-background [[data-slot=code-block]>&]:*:[button]:ring-1 [[data-slot=code-block]>&]:*:[button]:ring-foreground/10",
className
)}
{...props}
/>
)
}
function CodeBlockCopyButton({
value,
variant = "ghost",
size = "icon-xs",
className,
onClick,
...props
}: React.ComponentProps<typeof Button> & { value: string }) {
const [copied, setCopied] = React.useState(false)
React.useEffect(() => {
if (!copied) {
return
}
const timeout = setTimeout(() => setCopied(false), 2000)
return () => clearTimeout(timeout)
}, [copied])
return (
<>
<Button
data-slot="code-block-copy-button"
data-copied={copied || undefined}
variant={variant}
size={size}
aria-label={copied ? "Copied" : "Copy"}
className={cn(className)}
onClick={(event) => {
onClick?.(event)
navigator.clipboard
.writeText(value)
.then(() => setCopied(true))
.catch(() => {})
}}
{...props}
>
{copied ? (
<CheckIcon
/>
) : (
<CopyIcon
/>
)}
</Button>
<span aria-live="polite" className="sr-only">
{copied ? "Copied" : ""}
</span>
</>
)
}
function CodeBlockContent({
className,
children,
style,
wrap = false,
lineNumbers = false,
startLine = 1,
...props
}: React.ComponentProps<"pre"> & {
wrap?: boolean
lineNumbers?: boolean
startLine?: number
}) {
return (
<pre
data-slot="code-block-content"
data-wrap={wrap || undefined}
data-numbered={lineNumbers || undefined}
dir="ltr"
tabIndex={0}
style={{ counterReset: `line ${startLine - 1}`, ...style }}
className={cn(
"min-h-0 min-w-0 scroll-fade-x scrollbar-thin overflow-auto px-3 py-3 text-start font-mono leading-relaxed outline-none group-has-data-[slot=code-block-footer]/code-block:pb-2 group-has-data-[slot=code-block-header]/code-block:pt-2 group-data-[size=sm]/code-block:px-2.5 group-data-[size=sm]/code-block:py-2.5 group-data-[size=sm]/code-block:group-has-data-[slot=code-block-header]/code-block:pt-1.5 data-wrap:scroll-fade-y data-wrap:wrap-break-word data-wrap:whitespace-pre-wrap",
className
)}
{...props}
>
<code
data-slot="code-block-code"
className="grid min-h-lh min-w-max content-start in-data-numbered:grid-cols-[auto_1fr] in-data-wrap:min-w-0"
>
{lineNumbers && typeof children === "string"
? children
.replace(/\n$/, "")
.split("\n")
.map((line, index) => (
<CodeBlockLine key={index}>{line}</CodeBlockLine>
))
: children}
</code>
</pre>
)
}
const codeBlockLineVariants = cva(
"relative -mx-3 block min-h-lh px-3 group-data-[size=sm]/code-block:-mx-2.5 group-data-[size=sm]/code-block:px-2.5 before:min-w-6 before:text-end before:text-muted-foreground after:absolute after:start-1 after:top-0 after:text-muted-foreground after:select-none in-data-numbered:col-span-2 in-data-numbered:grid in-data-numbered:grid-cols-subgrid in-data-numbered:gap-x-3 in-data-numbered:[counter-increment:line] in-data-numbered:before:content-[counter(line)] in-data-numbered:before:select-none in-data-numbered:supports-[content:counter(line)_/_'']:before:content-[counter(line)_/_'']",
{
variants: {
variant: {
default: "",
added: "bg-muted after:content-['+']",
removed: "bg-destructive/10 after:text-destructive after:content-['−']",
highlighted: "bg-muted",
},
},
defaultVariants: {
variant: "default",
},
}
)
function CodeBlockLine({
className,
variant = "default",
children,
...props
}: React.ComponentProps<"span"> & VariantProps<typeof codeBlockLineVariants>) {
// An empty line holds a newline so copying a selection keeps blank lines.
const empty =
children === undefined ||
children === null ||
children === "" ||
(Array.isArray(children) && children.length === 0)
return (
<span
data-slot="code-block-line"
data-variant={variant}
className={cn(codeBlockLineVariants({ variant }), className)}
{...props}
>
<span data-slot="code-block-line-content" className="min-w-0">
{empty ? "\n" : children}
</span>
</span>
)
}
function CodeBlockFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="code-block-footer"
className={cn(
"flex min-w-0 shrink-0 items-center gap-2 px-3 pb-3 text-muted-foreground group-data-[size=sm]/code-block:px-2.5 group-data-[size=sm]/code-block:pb-2.5",
className
)}
{...props}
/>
)
}
export {
CodeBlock,
CodeBlockHeader,
CodeBlockTitle,
CodeBlockActions,
CodeBlockCopyButton,
CodeBlockContent,
CodeBlockLine,
CodeBlockFooter,
}