Skip to content
ContentCode blockMenu

Code block

Restrained syntax highlighting that keeps up with streaming, with copy, wrap, line numbers, highlighted lines, long-block collapse and multi-file tabs.

A file with line numbers and highlighted lines.

Installation

$ pnpm dlx shadcn@latest add https://chatcn.dev/r/code-block.json

Usage

tsx
import { CodeBlock } from "@/components/ui/chatcn/code-block"

<CodeBlock code={source} language="ts" filename="middleware.ts" showLineNumbers highlightLines="9-13" />

Highlighting uses one shared Shiki instance that loads with the first code block, then one grammar per language. Until a grammar arrives the code shows as plain text with the same metrics, so nothing moves when color arrives.

Examples

Streaming

With streaming, finished lines are tokenized once and kept; only the line being written is re-tokenized, continuing from the saved grammar state.

Highlighting keeps up line by line while the model writes.

Files

Several files in one block, with tabs.

Long blocks

Blocks over collapseAfter lines (24 by default) collapse behind Show N more lines. A block that streamed in never collapses, so the reader keeps their place.

Blocks over 24 lines collapse behind Show N more lines.

Languages

Bash, Python, a diff and wrapped SQL in the same restrained palette.

Theme

Tokens carry a role, and each role reads one variable: --chatcn-syntax-keyword (red), -string (deep blue), -constant (blue), -function (purple), -type (amber), -tag (green), with comments and punctuation on the gray ramp. Light and dark have their own tuned values. Override the variables in your theme to restyle every block, including the docs-style blocks rendered on the server.

Prefer gray code? Add chatcn-code-mono to <html> or to any container. Inside it, every role maps to a gray step instead of a hue.

Props

PropTypeNotes
codestringSource text
languagestringAliases such as ts, sh and py resolve
filenamestringShown instead of the language
streamingbooleanIncremental highlighting and the caret
showLineNumbers, startLineboolean, numberSticky gutter
highlightLinesnumber[] | "3-5,9"Marked lines
wrap, defaultWrap, onWrapChangeControlled or uncontrolled wrap
collapseAfternumber | falseDefault 24
actionsReactNodeExtra header actions, before Copy
hideHeaderbooleanBody only

CodeBlockTabs takes files: { name, code, language?, streaming?, highlightLines? }[], plus value, defaultValue and onValueChange. The language comes from the file extension when omitted.

Keyboard

KeyAction
TabFocus the code (to scroll it), wrap, copy and Show more
Arrow keysScroll the focused code, or move between file tabs

Accessibility

  • The code is a labeled, focusable region (for example TypeScript code), always left to right.
  • Wrap is a toggle button with aria-pressed. Copy announces Copied.
  • Line numbers are hidden from screen readers and from text selection.

Motion

  • The copy icon swaps to a check with a 150 ms blur crossfade.
  • Expanding a long block is instant: it is a large change, and animating height would only delay reading.