# CodeBlock

A multi-line snippet, on a plate, with a way to take it away.

- Group: Display
- Import: `import { CodeBlock } from '@misoto22/design'`
- Page: https://ui.misoto22.com/components/code-block/
- Related: code, markdown, article

## When to reach for it

Any snippet longer than a word. Pass html when a build-time highlighter has already run; pass code alone when it has not.

## Anatomy

- **Plate** (required) — The --paper-2 box on the --radius-lg corner with a --rule hairline. One elevated step, not a second surface colour.
- **Strip** — The bar along the top: title at the start, the language label at the end, the copy button after it. Present whenever there is anything to put in it, and never a hover-only affordance.
- **Copy button** — A ghost iconOnly Button that puts code — the string, never the rendered markup — on the clipboard, and flips its own accessible name to “Copied” for 1.6 seconds.
- **Body** (required) — A focusable, named role="group" that scrolls in both axes. Focusable because a scrollable box containing nothing focusable is unreachable by keyboard: there is nothing to Tab to, so the right-hand half of a long line does not exist without a mouse. A group and not a region, because a region is a landmark and a snippet is not one — three fenced blocks in an article would be three landmarks all called Code.
- **Line row** — One <span data-line> per line on the plain path, carrying its own number and its own highlight band. The number is a child of the line it numbers, so the two cannot come apart.

## Best practices

### Do

- Always pass code, even alongside html. It is what the copy button copies: a block that copies its rendered markup hands the reader a wall of spans, and one that scrapes textContent back out of the DOM is a non-breaking space away from pasting something that does not run.
- Highlight at build time and pass html. A highlighter is a few hundred kilobytes of grammar shipped to re-derive spans that never change, which would be the largest thing on the page.
- Give maxHeight to a long snippet rather than letting it run. The body scrolls and is focusable, so what is past the fold stays reachable by keyboard.

### Don’t

- lineNumbers and highlightLines are typed out of the html form and passing both is a compile error. They are a per-line structure, and html is one opaque string the component does not parse — which is why the type says so rather than the prop quietly rendering nothing.
- html is dangerouslySetInnerHTML. It is for the output of your own highlighter over your own source; markdown a reader wrote goes to code as a string, where it renders as text and cannot be mis-executed.
- Do not drop copyable to tidy the strip. The button is the reason a reader stops selecting a wrapped command by hand, and on a touch screen a manual selection is most of the interaction.

## Accessibility

- The scrolling body is tabbable and carries role="group" with a name, so its overflow is reachable with a keyboard and the tab stop announces what it is. Deliberately not role="region": that is a landmark, and a page with two code samples would put two of them in the landmark map under one name.
- The copy button is an iconOnly Button with a required aria-label that becomes “Copied” on success — the state change is announced rather than only drawn.
- The copy control clears the 44px pointer target on a coarse pointer, where the compact strip alone would not (WCAG 2.5.5).

## CodeBlock

A multi-line snippet, on a plate, with a way to take it away. The strip along the top carries the title, the language and the copy button, and it is there whenever there is anything to put in it. It is deliberately not a hover affordance: a control that appears on hover does not exist on a touch screen, which is where a reader is most likely to want the snippet and least able to select it by hand. Line numbers live INSIDE their own line's row rather than in a parallel gutter column. Two columns sharing a line-height align right up until one of them wraps, ships a different font, or scrolls on its own; a number that is a child of the line it numbers cannot come apart from it. The cost is that a number scrolls away with its line: it is inside the scrolling box, not in a gutter beside it. A gutter that stayed put would be a second column to keep in sync, which is the failure this avoids. The body is a focusable, named `role="group"`, and both halves of that are load-bearing. Focusable, because a scrollable box whose contents are not themselves focusable is unreachable by keyboard — there is nothing to Tab to and therefore no way to press an arrow key at it, so the right-hand half of a long line does not exist for anyone not using a mouse. Named, because a tab stop that announces nothing lands the reader in an anonymous box and leaves them to work out what they have arrived in. `group` rather than `region`, and that is the deliberate half. A region is a LANDMARK — one of the handful of major sections a reader navigates a whole page by — and a snippet is not one of those. Three fenced blocks in one article would put three landmarks called "Code" into that map, which is exactly the noise the `landmark-unique` rule exists to catch, and it would push the page's real landmarks down a list nobody can now skim. `group` carries the same accessible name to the same reader on the way in, and carries it nowhere else. For a function name inside a sentence, reach for `Code`.

### Props

- `code` (required) — `string`. The snippet, verbatim. Required even when `html` is supplied, and that is the point: this is what the copy button puts on the clipboard. A block that copies its own rendered markup hands the reader a wall of `<span>`s, and one that copies `textContent` scraped back out of the DOM is a single non-breaking space away from pasting something that does not run.
- `title` — `string`. A filename or a caption, printed at the start of the strip.
- `lang` — `string`. The language, printed at the end of the strip. Optional, and the omission is visible rather than silent: a block with no language gets no label instead of claiming a wrong one. A code block that does not say what it is written in makes the reader infer it from the syntax, which is the one thing they came to the block to learn.
- `maxHeight` — `number | string`. Caps the block's height and scrolls past it. A number is pixels; a string is any CSS length (`'24rem'`, `'50vh'`). The body scrolls in both axes and is focusable, so what is past the fold stays reachable with a keyboard rather than merely present in the DOM.
- `copyable` — `boolean` default `true`. Drops the copy button, for a block nobody is meant to run.
- `label` — `string`. Names the scrollable body for a screen reader. Defaults to `title`, and to "Code" when there is not one.
- `className` — `string`.
- `html` — `undefined`. Absent on this form; `string` on the other. Pass pre-highlighted markup — Shiki's output, run at build time over this same `code` — and the block renders that instead of the plain text. Doing so takes `lineNumbers` and `highlightLines` away with it.
- `lineNumbers` — `boolean` default `false`. Numbers every line, counting from one.
- `highlightLines` — `number[]`. Lines to band, counting from one. A number past the end of the snippet is ignored rather than throwing — the usual cause is a snippet that got shorter while the annotation did not.

## Example — numbered and banded

```tsx
import { CodeBlock } from '@misoto22/design'

<CodeBlock
  title="cn.ts"
  lang="ts"
  lineNumbers
  highlightLines={[5]}
  maxHeight="18rem"
  code={SOURCE}
/>
```

## Example — a command to copy

```tsx
import { CodeBlock } from '@misoto22/design'

<div className="flex w-full flex-col gap-3">
  <CodeBlock code="pnpm add @misoto22/design" lang="bash" label="Install command" />
  <CodeBlock code="pnpm --filter @misoto22/design build" lang="bash" label="Build command" />
</div>
```

## Example — highlighted somewhere else

```tsx
import { CodeBlock } from '@misoto22/design'

<CodeBlock title="tone.ts" lang="ts" code={SOURCE} html={HIGHLIGHTED} />
```

## Example — output nobody runs

```tsx
import { CodeBlock } from '@misoto22/design'

<CodeBlock code={OUTPUT} copyable={false} label="Build output" maxHeight="12rem" />
```
