CodeBlock
Syntax-highlighted code display with single-file and multi-tab modes, a copy button, line highlighting, wrap and collapse — on a self-contained neutral plate that can follow or lock its theme.
pnpm add @tessinaui/uiWhen to use
- Static code people will read or copy: a snippet in docs, an API example, a code answer in an AI chat. It highlights, copies, wraps and collapses, and it paints its own neutral plate so it reads on any surface.
- An install command or one-liner. Hide the header with
showHeader={false}and the copy control floats over the code (Command). - The same command for several tools or files. Give
tabsone entry each, for package managers or a set of files, instead of stacking blocks (Multi-tab). - A long listing that should not dominate the page. Cap it with
maxHeightso the block collapses behind a show more toggle (Collapsible). - Pointing at the lines that matter. Use
highlightLineswithshowLineNumbersso the prose can refer to a line.
When not to use
- Inline code inside a sentence. Use a
<code>element;CodeBlockis always a block. - Code the reader edits. The block is read-only. Use a Textarea for anything the user types or changes.
- A live terminal or log stream. A scrolling console with timestamps and filters is a log viewer, not a code block. Render the stream in its own component.
- A diff. Added and removed rows with dual gutters and split or unified
views are their own control; a
CodeBlockshows a snapshot, not a change. - A single short value people can select easily, such as a key shown once.
A plain
<code>element is lighter than a block with a header.
Installation
pnpm add @tessinaui/ui react-syntax-highlighter @types/react-syntax-highlighterUsage
import { CodeBlock } from "@tessinaui/ui";<CodeBlock
language="typescript"
filename="utils.ts"
code={`export const add = (a: number, b: number) => a + b;`}
/>Examples
Default
A single-file code block with a filename and copy button.
Command
Hide the header with showHeader={false} for a bare snippet — an install command or
a one-liner — with the copy control floating over the code. This is the dominant
shape in developer docs.
Multi-tab
Provide tabs to switch between multiple files, each with its own language and copy
state. The tab bar is one keyboard tab stop; arrow keys move between files.
Package managers
The tabs pattern is how a docs page offers the same command for npm, pnpm, yarn and bun.
Line highlighting
Highlight specific lines with highlightLines, paired with showLineNumbers for
clarity.
Collapsible
Cap the height with maxHeight to reveal an expand/collapse toggle for long blocks.
showMoreLabel / showLessLabel relabel the toggle.
Wrap
By default a long line scrolls horizontally. Set showLineWrap to wrap it instead —
useful for prose-like commands where horizontal scrolling hides the tail.
Sizes
Three sizes — sm, md, and lg — scale the whole block: code padding, the code
type scale, and the chrome around it (header height, filename, copy control, tab
bar, expand row). Type follows the shared data-display ladder, with the header one
step below the code it labels.
Header actions
Pass any controls to headerActions — a download button, an "open in playground"
link, a language Select — and they sit in the header before the copy button.
Theme
The plate is self-contained. theme="auto" (default) follows the page; theme="dark"
or theme="light" locks it — the dark-code-on-a-light-docs-page convention.
Error
A clipboard write can be refused — sandboxed iframes, unfocused pages, embedded
webviews. The copy control then shows Failed and announces "Couldn't copy to
clipboard" through the same polite live region that confirms a success, so nobody
pastes stale text believing the copy worked. writeToClipboard replaces the built-in
write for hosts with their own clipboard bridge; resolve false or throw to report a
failure, as this example does.
Loading
CodeBlockSkeleton renders a placeholder that matches the block's box while code is
being fetched.
On color
The block stands on a coloured Surface as its own panel: its fills are opaque and its syntax palette and chrome inks are the plate's own, so nothing shows through and nothing re-inks. Header actions inside the block read the block's plate, never the surface around it.
Language identifiers
language accepts any identifier supported by Prism. Common values:
typescript · tsx · javascript · jsx · bash · shell · python · css · scss · json · yaml · markdown · html · sql · rust · go
Pass "text" or omit language in a tab to display plain text with no highlighting.
API Reference
CodeBlock
Either code + language (single-file) or tabs (multi-file) is required. The
root forwards className and any other div attributes (data-*, id, aria-*).
| Prop | Type | Default | Description |
|---|---|---|---|
code | string | — | Code string — single-file mode |
language | string | — | Prism language identifier |
filename | string | — | Shown in the header; when omitted the language badge is used |
highlightLines | number[] | [] | 1-indexed line numbers to highlight |
tabs | CodeBlockTab[] | — | Multi-tab mode — replaces code, language, filename |
theme | "auto" | "light" | "dark" | "auto" | auto follows the page; light / dark lock the plate |
size | "sm" | "md" | "lg" | "md" | Padding, code type scale, and header/tab/expand chrome |
rounded | "none" | "sm" | "md" | "lg" | "full" | "lg" | Corner radius — full caps at the panel radius |
showLineNumbers | boolean | false | Renders the line-number gutter |
showLineWrap | boolean | false | Wrap long lines instead of scrolling horizontally |
showHeader | boolean | true | Single-code mode only — when false the copy control floats |
showLangBadge | boolean | true | Show the language badge when no filename is set |
headerActions | ReactNode | — | Extra controls in the header, before the copy button |
writeToClipboard | (code: string) => boolean | Promise<boolean> | — | Replaces the built-in clipboard write; false or a throw shows and announces a failed copy |
maxHeight | string | number | — | Caps block height and adds an expand/collapse toggle |
showMoreLabel | string | "Show more" | Expand-control label when collapsed |
showLessLabel | string | "Show less" | Expand-control label when expanded |
defaultCollapsed | boolean | true | Initial collapsed state when maxHeight is set |
collapsed | boolean | — | Controlled collapsed state |
onCollapsedChange | (collapsed: boolean) => void | — | Fires when the expand/collapse button is clicked |
CodeBlockSkeleton
Mirrors the block's box (§2b). Takes size, rounded, theme, plus
lines (default 6), showHeader, showFilename, showLangBadge,
showLineNumbers and maxHeight, and forwards className + div attributes.
CodeBlockTab
interface CodeBlockTab {
name: string; // Label shown in the tab button
code: string; // Code content for this tab
language?: string; // Prism language (defaults to "text")
highlightLines?: number[]; // 1-indexed lines to highlight in this tab
}Accessibility
Pattern: WAI-ARIA Tabs in multi-file mode; a single block is a named group.
- Multi-file mode is a real tablist.
tabsrendersrole="tablist"(aria-label="Code tabs", horizontal) with a roving tabindex — one tab stop, arrow keys between files — and eachrole="tab"carriesaria-selectedandaria-controls. The code is therole="tabpanel", labelled by its tab. - A single block is a named group, named by
filename, else the language, else "Code". - The code region is focusable (
tabIndex={0}), so a keyboard user can scroll a long line that does not wrap. SetshowLineWrapwhen horizontal scrolling would hide the part that matters. - Copy confirms out loud. The button's name swaps from "Copy code" to "Copied to
clipboard", and because a swapped name alone is not reliably announced, a
visually hidden polite
role="status"region speaks the same words. It only does so when the clipboard write succeeded. - A failed copy is announced too. The control shows Failed, its name becomes "Couldn't copy to clipboard", and the same region speaks it. The region is mounted empty from first render, and a repeated failure is announced again.
- The expand toggle (
maxHeight) carriesaria-expandedandaria-controls;showMoreLabel/showLessLabelare its name. - Decoration is hidden. The copy and expand icons are
aria-hidden;CodeBlockSkeletonis a singlerole="status"with a visually hidden "Loading", written 150ms after mount. - Contrast — the syntax palette is literal colour, not tokens, so it is gated
token-by-token in both themes by
code-block.contrast.test.tsxinstead ofaudit:contrast: every ink must clear AA on the bare plate and on the highlighted-line band.
Keyboard
| Key | Context | Action |
|---|---|---|
Tab / Shift+Tab | Anywhere | Tab bar (one stop), headerActions, copy button, code region, expand toggle — natural order |
← / → | Tab bar | Previous / next file, wrapping at the ends |
Home / End | Tab bar | First / last file |
Enter / Space | Copy button, expand toggle | Copies the code / toggles the height cap |
← / → | Focused code region | Scrolls a long line horizontally |
AT matrix
| AT | Status |
|---|---|
| VoiceOver + Safari (macOS) | Passed 2026-09-14 after a fix — the copy button reads "Copy code, button" and copying announces "Copied to clipboard". The result region used to sit inside the button, where WebKit never exposes a live region, so copying was silent |
| VoiceOver + Chrome (macOS) | UNTESTED |
| NVDA + Chrome (Windows) | UNTESTED — no Windows host available |
| JAWS | UNTESTED |
Status stays beta until one VoiceOver and one NVDA pass are on record (QA flow §5.6).