# AI chat with code fences

> Streaming assistant message rendering markdown code fences.

A shadcn/ui block from the [@reui](https://reui.io) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/reui/c-code-block-4).

- Page, with a live preview: https://shadcn.exchange/r/reui/c-code-block-4
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/reui/c-code-block-4-light-cd9471ca3c36a1a1.png
- Registry item: https://reui.io/r/new-york-v4/c-code-block-4.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://reui.io/r/new-york-v4/c-code-block-4.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `avatar`, `@reui/code-block`, `@reui/frame`

## Files

### `c-code-block-4.tsx`

```tsx
"use client"

import { useEffect, useState } from "react"
import {
  CodeBlock,
  CodeBlockCopyButton,
  markdownFences,
} from "@/components/reui/code-block/code-block"
import {
  Frame,
  FrameDescription,
  FrameHeader,
  FramePanel,
  FrameTitle,
} from "@/components/reui/frame"

import {
  Avatar,
  AvatarFallback,
  AvatarImage,
} from "@/components/ui/avatar"

const reply = `Use the streaming helper, then render it:

\`\`\`tsx
const { messages } = useChat({ api: "/api/chat" })
\`\`\`

That keeps the transcript in sync.`

/*
 * `markdownFences` flags a fence whose closing delimiter has not arrived as
 * `open`, which is what lets the transcript render the partial block mid
 * stream instead of dropping it until the closer lands.
 */
export function Pattern() {
  const [length, setLength] = useState(0)

  useEffect(() => {
    if (length >= reply.length) return
    const id = window.setTimeout(() => setLength((value) => value + 4), 30)
    return () => window.clearTimeout(id)
  }, [length])

  const streamed = reply.slice(0, length)
  const streaming = length < reply.length

  return (
    <Frame dense className="w-full max-w-2xl">
      <FrameHeader>
        <FrameTitle>Assistant</FrameTitle>
        <FrameDescription>
          Generated responses may contain mistakes.
        </FrameDescription>
      </FrameHeader>

      <FramePanel className="flex flex-col gap-4">
        <div className="flex gap-3">
          <Avatar className="size-7 shrink-0 rounded-full">
            <AvatarImage
              src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=96&h=96&dpr=2&q=80"
              alt="Mira Stone"
            />
            <AvatarFallback className="text-[10px] font-medium">
              MS
            </AvatarFallback>
          </Avatar>
          <p className="pt-1 text-sm">How do I stream chat messages?</p>
        </div>

        <div className="flex gap-3">
          <Avatar className="size-7 shrink-0 rounded-full">
            <AvatarFallback className="bg-muted text-muted-foreground text-[10px] font-medium">
              AI
            </AvatarFallback>
          </Avatar>
          <div className="flex min-w-0 flex-1 flex-col gap-2 pt-1">
            {markdownFences(streamed).map((part, index) =>
              part.type === "text" ? (
                <p key={index} className="text-sm whitespace-pre-wrap">
                  {part.content}
                </p>
              ) : (
                <CodeBlock
                  key={index}
                  code={part.content}
                  language={part.language}
                  streaming={streaming && part.open}
                >
                  <CodeBlockCopyButton />
                </CodeBlock>
              )
            )}
          </div>
        </div>
      </FramePanel>
    </Frame>
  )
}
```
