# Code Block

> Syntax-highlighted code with a filename header, copy, line numbers, highlighted lines, diffs, wrap, collapsible height and streaming-safe rendering.

- **Slug:** `code-block` · **Category:** conversation · **Status:** beta
- **Import:** `import { CodeBlock, CodeToken, CodeTokenType } from "@kura-ui/react/code-block";`
- **Docs:** https://kuro.abstergo.fyi/components/code-block
- **Registry item:** https://kuro.abstergo.fyi/r/code-block.json
- **Tags:** code, syntax-highlighting, diff, copy, streaming, snippet, pre, markdown

## Install

```bash
# shadcn registry (copies source into your project)
npx shadcn@latest add https://kuro.abstergo.fyi/r/code-block.json
# Kura CLI (same, plus dependency resolution and --json output)
npx @kura-ui/cli add code-block
# npm package
npm install @kura-ui/react
```

## When to use

- Show code a model wrote in a chat answer, including while it is still streaming.
- Show a file or snippet with a filename header and a copy button (docs, READMEs, agent output).
- Show an agent's proposed edit as a unified diff with +/- lines.
- Long files that should start collapsed to a fixed height with a “Show N more lines” control.
- Terminal commands (language="bash") with an extra header action such as Run.

## When not to use

- Inline code inside a sentence — use a plain <code> element.
- Tool arguments/results JSON inside an agent step — use tool-call, which pretty-prints JSON for you.
- Prose output from a model — use streaming-text or message.
- Editable code — use a real editor (CodeMirror/Monaco); CodeBlock is read-only.

## Props

| Prop | Type | Default | Required | Description |
| --- | --- | --- | --- | --- |
| `code` | `string` | — | yes | The source to display. For diffs pass unified-diff lines prefixed with `+`, `-` or a space (`@@`, `+++`, `---` lines render as meta). |
| `language` | `string` | `"text"` | no | Language id: ts/tsx/js/jsx, json, bash/sh, python, css, html/xml, yaml, sql, go, rust, java, … (see `supportedLanguages`). Unknown ids render as plain text. |
| `filename` | `string` | — | no | File name shown in the header, e.g. "app/page.tsx". |
| `lineNumbers` | `boolean` | `false` | no | Show a tabular line-number gutter (not selectable, aria-hidden). |
| `highlightLines` | `readonly number[]` | — | no | 1-based line numbers to emphasise with a tinted row and a left rule. |
| `diff` | `boolean` | — | no | Render `+`/`-` prefixed lines as additions/removals (green/red row, sign gutter, screen-reader “added:/removed:”). Defaults to true when `language` is "diff"; otherwise the content after the sign is highlighted with `language`. |
| `streaming` | `boolean` | `false` | no | Code is still arriving. Only complete lines are tokenized (memoized), the unfinished last line renders plain until its newline lands, and a block caret marks the live edge — no flicker from half-open strings or comments. |
| `wrap` | `boolean` | — | no | Soft-wrap long lines (controlled). |
| `defaultWrap` | `boolean` | `false` | no | Initial wrap state when uncontrolled. |
| `onWrapChange` | `(wrap: boolean) => void` | — | no | Called when the wrap toggle is pressed. |
| `maxHeight` | `number` | — | no | Collapse code taller than this many px, with a bottom fade and a “Show N more lines” pill; the header gets an expand/collapse button. |
| `expanded` | `boolean` | — | no | Expanded past `maxHeight` (controlled). |
| `defaultExpanded` | `boolean` | `false` | no | Initial expanded state when uncontrolled. |
| `onExpandedChange` | `(expanded: boolean) => void` | — | no | Called when the code is expanded or collapsed. |
| `copyable` | `boolean` | `true` | no | Show the copy button (icon cross-fades to a check for 1.6s on success). |
| `onCopy` | `(code: string) => void` | — | no | Called with the copied text after a successful copy. |
| `header` | `boolean` | `true` | no | Show the header row (filename/language and actions). Without it, no wrap or copy button is shown. |
| `actions` | `React.ReactNode` | — | no | Extra header actions rendered before wrap/copy, e.g. a ghost xs Button “Run” or “Apply”. |

Native attributes of the root element, `className` and `ref` are passed through.

## Anatomy

- **CodeBlock** (`data-slot="code-block"`) — Root. `data-language`, `data-wrap`, `data-streaming`, `data-state="collapsed" | "expanded"` (only when collapsible).
- **Header** (`data-slot="code-block-header"`) — Filename/language and actions.
- **Title** (`data-slot="code-block-title"`) — Filename, or the language when no filename is given.
- **Language** (`data-slot="code-block-language"`) — Language badge, shown next to a filename.
- **Body** (`data-slot="code-block-body"`) — Height-limited wrapper (transitions max-height when expanding).
- **Code** (`data-slot="code-block-code"`) — The <code> element holding the lines.
- **Line** (`data-slot="code-block-line"`) — `data-line`, `data-highlighted`, `data-diff="add" | "remove" | "context" | "meta"`.
- **LineNumber** (`data-slot="code-block-line-number"`) — Gutter number (aria-hidden).
- **Caret** (`data-slot="code-block-caret"`) — Streaming caret (aria-hidden).
- **Expand** (`data-slot="code-block-expand"`) — Bottom fade + “Show N more lines” pill while collapsed.

## Accessibility

- Role: `region`
- The scroll area is a focusable `role="region"` labelled “<filename or language> code”, so keyboard users can scroll wide code with the arrow keys.
- Copy, wrap and expand are labelled icon buttons with tooltips; wrap exposes `aria-pressed`, expand exposes `aria-expanded`.
- A polite status region announces “Copied to clipboard”.
- Line numbers, diff signs and the caret are aria-hidden; removed/added lines get visually hidden “removed:”/“added:” prefixes.
- Syntax colours derive from --kura-fg-muted, keeping its ≥ 4.5:1 contrast in both themes.
- Reduced motion: The copy icon swap becomes a pure cross-fade (no scale/blur), the streaming caret stops pulsing and expanding is instant.

| Keys | Action |
| --- | --- |
| Tab | Moves through header actions, then the scrollable code region |
| ← / → | Scrolls wide code horizontally when the region is focused |
| Enter / Space | Activates copy, wrap or expand |

## Notes for agents

- Highlighting is built in, synchronous and dependency-free (no shiki/prism needed); unknown languages render as plain text.
- While a model is still writing code, pass `streaming` and the growing string as `code`; set it to false when the fence closes.
- For diffs keep the leading `+`/`-`/space on every line and set `diff` (or language="diff" for no inner highlighting).
- Theme the palette with CSS variables on any ancestor: --kura-syntax-keyword, -string, -constant, -function, -type, -tag, -attr, -property.
- `highlightCode(code, language)` is exported for custom renderers; it returns tokens grouped by line.

## Examples

### Snippet (minimal)

Code + language. Header shows the language; copy and wrap are built in.

```tsx
import { CodeBlock } from "@kura-ui/react/code-block";

const SNIPPET = `import { StreamingText } from "@kura-ui/react/streaming-text";

export function Answer({ stream }: { stream: AsyncIterable<string> }) {
  // Bursty tokens are evened out into a calm cadence.
  return <StreamingText stream={stream} speed={90} className="max-w-prose" />;
}`;

export function CodeBlockMinimal() {
  return <CodeBlock code={SNIPPET} language="tsx" className="w-full max-w-xl" />;
}
```

### File with every option (full)

Filename, line numbers, highlighted lines, controlled wrap, collapsed to 300px, onCopy.

```tsx
"use client";

import * as React from "react";
import { CodeBlock } from "@kura-ui/react/code-block";

const ROUTE = `import { anthropic } from "@ai-sdk/anthropic";
import { streamText } from "ai";

export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: anthropic("claude-opus-5-5"),
    system: "You are a calm, precise assistant.",
    messages,
    temperature: 0.4,
  });

  return result.toUIMessageStreamResponse();
}

// Tokens arrive in bursts; the client smooths them with <StreamingText />.
export const runtime = "edge";
const RETRIES = 3;
const TIMEOUT_MS = 12_000;`;

export function CodeBlockFull() {
  const [wrap, setWrap] = React.useState(false);
  const [copies, setCopies] = React.useState(0);
  return (
    <div className="flex w-full max-w-xl flex-col gap-2">
      <CodeBlock
        code={ROUTE}
        language="ts"
        filename="app/api/chat/route.ts"
        lineNumbers
        highlightLines={[9, 10, 11, 12, 13, 14]}
        wrap={wrap}
        onWrapChange={setWrap}
        maxHeight={300}
        defaultExpanded={false}
        copyable
        onCopy={() => setCopies((c) => c + 1)}
        header
      />
      <p className="font-kura-mono text-kura-fg-subtle text-xs tabular-nums">
        wrap: {String(wrap)} · copied {copies}×
      </p>
    </div>
  );
}
```

### Diff (variant)

A proposed edit as a unified diff, highlighted as TSX.

```tsx
"use client";

import * as React from "react";
import { CodeBlock } from "@kura-ui/react/code-block";

const DIFF = `@@ -4,7 +4,8 @@ export function Answer() {
   const [open, setOpen] = React.useState(false);
-  const items = data.filter((d) => d.visible);
+  const items = React.useMemo(() => data.filter((d) => d.visible), [data]);
+  const reduced = useReducedMotion();
   return (
-    <List items={items} animate />
+    <List items={items} animate={!reduced} />
   );`;

export function CodeBlockDiff() {
  return (
    <CodeBlock
      code={DIFF}
      language="tsx"
      diff
      filename="components/answer.tsx"
      lineNumbers
      className="w-full max-w-xl"
    />
  );
}
```

### Streaming (state)

Code arriving a few characters at a time — the open last line never flickers.

```tsx
"use client";

import { RotateCcw } from "lucide-react";
import * as React from "react";
import { Button } from "@kura-ui/react/button";
import { CodeBlock } from "@kura-ui/react/code-block";

const SNIPPET = `import { StreamingText } from "@kura-ui/react/streaming-text";

export function Answer({ stream }: { stream: AsyncIterable<string> }) {
  // Bursty tokens are evened out into a calm cadence.
  return <StreamingText stream={stream} speed={90} className="max-w-prose" />;
}`;

export function CodeBlockStreaming() {
  const [run, setRun] = React.useState(0);
  const [length, setLength] = React.useState(0);
  // biome-ignore lint/correctness/useExhaustiveDependencies: `run` restarts the fake stream
  React.useEffect(() => {
    setLength(0);
    let n = 0;
    const id = setInterval(() => {
      n = Math.min(SNIPPET.length, n + 2 + Math.floor(Math.random() * 7));
      setLength(n);
      if (n >= SNIPPET.length) clearInterval(id);
    }, 45);
    return () => clearInterval(id);
  }, [run]);
  const done = length >= SNIPPET.length;
  return (
    <div className="flex w-full max-w-xl flex-col items-end gap-2">
      <CodeBlock
        code={SNIPPET.slice(0, length)}
        language="tsx"
        filename="answer.tsx"
        streaming={!done}
        lineNumbers
        className="w-full"
      />
      <Button variant="secondary" size="sm" onClick={() => setRun((r) => r + 1)}>
        <RotateCcw /> Replay
      </Button>
    </div>
  );
}
```

### Command with a Run action (composition)

A bash block with a header Button, followed by a header-less output block.

```tsx
"use client";

import { Play } from "lucide-react";
import * as React from "react";
import { Button } from "@kura-ui/react/button";
import { CodeBlock } from "@kura-ui/react/code-block";

export function CodeBlockComposition() {
  const [ran, setRan] = React.useState(false);
  return (
    <div className="flex w-full max-w-xl flex-col gap-2">
      <CodeBlock
        code={"pnpm dlx @kura-ui/cli add code-block\npnpm --filter web dev --port 3000"}
        language="bash"
        actions={
          <Button variant="ghost" size="xs" onClick={() => setRan(true)}>
            <Play /> Run
          </Button>
        }
      />
      {ran && (
        <CodeBlock
          code={"✓ added code-block (4 files)\n▲ ready on http://localhost:3000"}
          header={false}
          copyable={false}
          className="animate-kura-rise-in"
        />
      )}
    </div>
  );
}
```

## Dependencies

- npm: `lucide-react`
- Kura registry items: `kura-theme`, `icon-button`, `use-controllable-state`, `utils`
- Related: `tool-call`, `message`, `streaming-text`, `approval-prompt`
