# Code block

Code on display glass. A bar along the top holds the file name on a tab, with a lit edge under it, and the copy key at the far end. Under it the lines run, each with its number engraved in a gutter that stays where it is when a long line scrolls sideways. The numbers are drawn by the stylesheet, so they are never selected, never copied and never read aloud. Highlighting is optional and small: a few strengths of the display’s own light, told apart by weight and italic, never a colour of its own.

Kit part · Free · https://tactileui.dev/parts/code-block

- Element: `<figure class="tui-glass tui-code-block">`
- Needs: `kit.css` and `kit.mjs` ([how to install the kit](https://tactileui.dev/docs/installation#the-kit))
- Categories: Code & devtools
- Finishes: Aluminium, Graphite, Oak, Walnut, Porcelain, Black, Daylight, Night, Paper, Phosphor, E-paper, AMOLED
- Volumes: Tactile, Quiet

## Variants

### With a file name · lit JavaScript

```html
<div style="width:min(560px,calc(100vw - 96px));text-align:left"><figure class="tui-glass tui-code-block"><div class="tui-code-block__bar"><figcaption class="tui-code-block__tab"><span>feed.mjs</span></figcaption><button class="tui-key tui-key--flat tui-key--sm tui-copy-key" type="button" data-copy-from="#cb3" aria-label="Copy feed.mjs"></button></div><div class="tui-code-block__scroll" tabindex="0" role="region" aria-label="feed.mjs, code"><pre class="tui-code-block__pre"><code class="tui-code-block__code" id="cb3" data-lang="js" data-lit><span class="tui-code-block__line"><span class="tui-tok-k">import</span> { connect } <span class="tui-tok-k">from</span> <span class="tui-tok-s">'@northwind/cli'</span>;
</span><span class="tui-code-block__line">
</span><span class="tui-code-block__line"><span class="tui-tok-c">// Open the harbour feed and log each berth change.</span>
</span><span class="tui-code-block__line"><span class="tui-tok-k">const</span> feed = <span class="tui-tok-k">await</span> connect({ site: <span class="tui-tok-s">'quay-street'</span>, retries: <span class="tui-tok-n">3</span> });
</span><span class="tui-code-block__line">feed.on(<span class="tui-tok-s">'berth'</span>, (change) =&gt; {
</span><span class="tui-code-block__line">  console.log(<span class="tui-tok-s">'Berth '</span> + change.id + <span class="tui-tok-s">' is now '</span> + change.state);
</span><span class="tui-code-block__line">});
</span></code></pre></div></figure></div>
```

### A language legend, no file name · shell

```html
<div style="width:min(560px,calc(100vw - 96px));text-align:left"><figure class="tui-glass tui-code-block"><div class="tui-code-block__bar"><figcaption class="tui-label tui-code-block__lang">sh</figcaption><button class="tui-key tui-key--flat tui-key--sm tui-copy-key" type="button" data-copy-from="#cb4" aria-label="Copy Shell commands"></button></div><div class="tui-code-block__scroll" tabindex="0" role="region" aria-label="Shell commands, code"><pre class="tui-code-block__pre"><code class="tui-code-block__code" id="cb4" data-lang="sh" data-lit><span class="tui-code-block__line"><span class="tui-tok-p">$ </span>npm install @northwind/cli
</span><span class="tui-code-block__line"><span class="tui-tok-p">$ </span>northwind init quay-street
</span><span class="tui-code-block__line"><span class="tui-tok-p">$ </span>northwind deploy --dry-run   <span class="tui-tok-c"># nothing is changed</span>
</span></code></pre></div></figure></div>
```

### Plain text · no data-lang, nothing lit

```html
<div style="width:min(400px,calc(100vw - 96px));text-align:left"><figure class="tui-glass tui-code-block"><div class="tui-code-block__bar"><figcaption class="tui-code-block__tab"><span>northwind.json</span></figcaption><button class="tui-key tui-key--flat tui-key--sm tui-copy-key" type="button" data-copy-from="#cb5" aria-label="Copy northwind.json"></button></div><div class="tui-code-block__scroll" tabindex="0" role="region" aria-label="northwind.json, code"><pre class="tui-code-block__pre"><code class="tui-code-block__code" id="cb5"><span class="tui-code-block__line">{
</span><span class="tui-code-block__line">  &quot;site&quot;: &quot;quay-street&quot;,
</span><span class="tui-code-block__line">  &quot;retries&quot;: 3,
</span><span class="tui-code-block__line">  &quot;berths&quot;: [&quot;A1&quot;, &quot;A2&quot;, &quot;B1&quot;],
</span><span class="tui-code-block__line">  &quot;dryRun&quot;: true
</span><span class="tui-code-block__line">}
</span></code></pre></div></figure></div>
```

### A long line scrolls sideways, the gutter stays

```html
<div style="width:min(360px,calc(100vw - 96px));text-align:left"><figure class="tui-glass tui-code-block"><div class="tui-code-block__bar"><figcaption class="tui-code-block__tab"><span>schedule.js</span></figcaption><button class="tui-key tui-key--flat tui-key--sm tui-copy-key" type="button" data-copy-from="#cb6" aria-label="Copy schedule.js"></button></div><div class="tui-code-block__scroll" tabindex="0" role="region" aria-label="schedule.js, code"><pre class="tui-code-block__pre"><code class="tui-code-block__code" id="cb6" data-lang="js" data-lit><span class="tui-code-block__line"><span class="tui-tok-k">const</span> schedule = <span class="tui-tok-s">'https://harbour.northwind.example/api/v2/berths/quay-street/schedules?from=2026-10-07&amp;to=2026-10-14&amp;include=tides,pilots,tugs,fuel'</span>;
</span><span class="tui-code-block__line"><span class="tui-tok-k">const</span> rows = <span class="tui-tok-k">await</span> fetch(schedule).then((r) =&gt; r.json());
</span></code></pre></div></figure></div>
```

### From line 24, with two lines marked

```html
<div style="width:min(560px,calc(100vw - 96px));text-align:left"><figure class="tui-glass tui-code-block" style="--cb-start:24"><div class="tui-code-block__bar"><figcaption class="tui-code-block__tab"><span>feed.mjs</span></figcaption><button class="tui-key tui-key--flat tui-key--sm tui-copy-key" type="button" data-copy-from="#cb7" aria-label="Copy feed.mjs"></button></div><div class="tui-code-block__scroll" tabindex="0" role="region" aria-label="feed.mjs, code"><pre class="tui-code-block__pre"><code class="tui-code-block__code" id="cb7" data-lang="js" data-lit><span class="tui-code-block__line"><span class="tui-tok-k">import</span> { connect } <span class="tui-tok-k">from</span> <span class="tui-tok-s">'@northwind/cli'</span>;
</span><span class="tui-code-block__line">
</span><span class="tui-code-block__line"><span class="tui-tok-c">// Open the harbour feed and log each berth change.</span>
</span><span class="tui-code-block__line" data-mark><span class="tui-tok-k">const</span> feed = <span class="tui-tok-k">await</span> connect({ site: <span class="tui-tok-s">'quay-street'</span>, retries: <span class="tui-tok-n">3</span> });
</span><span class="tui-code-block__line" data-mark>feed.on(<span class="tui-tok-s">'berth'</span>, (change) =&gt; {
</span><span class="tui-code-block__line">  console.log(<span class="tui-tok-s">'Berth '</span> + change.id + <span class="tui-tok-s">' is now '</span> + change.state);
</span><span class="tui-code-block__line">});
</span></code></pre></div></figure></div>
```

### No gutter · .tui-code-block--bare

```html
<div style="width:min(480px,calc(100vw - 96px));text-align:left"><figure class="tui-glass tui-code-block tui-code-block--bare"><div class="tui-code-block__bar"><figcaption class="tui-label tui-code-block__lang">sh</figcaption><button class="tui-key tui-key--flat tui-key--sm tui-copy-key" type="button" data-copy-from="#cb8" aria-label="Copy Shell commands"></button></div><div class="tui-code-block__scroll" tabindex="0" role="region" aria-label="Shell commands, code"><pre class="tui-code-block__pre"><code class="tui-code-block__code" id="cb8" data-lang="sh" data-lit><span class="tui-code-block__line"><span class="tui-tok-p">$ </span>npm install @northwind/cli
</span><span class="tui-code-block__line"><span class="tui-tok-p">$ </span>northwind init quay-street
</span><span class="tui-code-block__line"><span class="tui-tok-p">$ </span>northwind deploy --dry-run   <span class="tui-tok-c"># nothing is changed</span>
</span></code></pre></div></figure></div>
```

### A fixed height scrolls both ways · --cb-max

```html
<div style="width:min(480px,calc(100vw - 96px));text-align:left"><figure class="tui-glass tui-code-block" style="--cb-max:96px"><div class="tui-code-block__bar"><figcaption class="tui-code-block__tab"><span>feed.mjs</span></figcaption><button class="tui-key tui-key--flat tui-key--sm tui-copy-key" type="button" data-copy-from="#cb9" aria-label="Copy feed.mjs"></button></div><div class="tui-code-block__scroll" tabindex="0" role="region" aria-label="feed.mjs, code"><pre class="tui-code-block__pre"><code class="tui-code-block__code" id="cb9" data-lang="js" data-lit><span class="tui-code-block__line"><span class="tui-tok-k">import</span> { connect } <span class="tui-tok-k">from</span> <span class="tui-tok-s">'@northwind/cli'</span>;
</span><span class="tui-code-block__line">
</span><span class="tui-code-block__line"><span class="tui-tok-c">// Open the harbour feed and log each berth change.</span>
</span><span class="tui-code-block__line"><span class="tui-tok-k">const</span> feed = <span class="tui-tok-k">await</span> connect({ site: <span class="tui-tok-s">'quay-street'</span>, retries: <span class="tui-tok-n">3</span> });
</span><span class="tui-code-block__line">feed.on(<span class="tui-tok-s">'berth'</span>, (change) =&gt; {
</span><span class="tui-code-block__line">  console.log(<span class="tui-tok-s">'Berth '</span> + change.id + <span class="tui-tok-s">' is now '</span> + change.state);
</span><span class="tui-code-block__line">});
</span></code></pre></div></figure></div>
```

## Every state

- Rest
- Copy key focused
- Lines focused
- A marked line

Hover, pressed and focus are forced with `.is-hover`, `.is-down` and `.is-focus`; checked and disabled are real.

## API

| Name | Kind | Values | What it does |
| - | - | - | - |
| figure.tui-glass.tui-code-block | element | both classes | The block: display glass with no padding of its own, so the bar and the lines reach its edges. |
| .tui-code-block__bar | element | the top row | Holds the tab at the start and the copy key at the end, with a rule under it. |
| figcaption.tui-code-block__tab > span | element | the file name | The tab. A lit edge under it says it is the file that is open. Use a figcaption.tui-label.tui-code-block__lang instead when there is only a language to say. |
| button.tui-copy-key[data-copy-from] | element | points at the ’s id | The kit’s copy key, flat and small. It copies the text of the code, which has no numbers in it. Give it an aria-label that names the file. |
| .tui-code-block__scroll[tabindex="0"][role="region"] | element | named | The scrolling region, a stop of its own so the keyboard can scroll long lines. Its aria-label says what the code is. |
| code.tui-code-block__code[data-lang] | element | js · ts · json · sh · css · yaml | The code. With data-lang the script lights a few tokens; with none it only splits the lines. data-mark="3,4" lights those lines. |
| --cb-start · --cb-max | custom property | a number · a length | The number the first line has, and a height after which the block scrolls up and down. |
| --bare | modifier | tui-code-block--bare | Takes the gutter away, for a snippet that is not a listing. |
| codeBlockHTML() · codeLinesHTML() · wireCodeBlocks() | function | needs kit.mjs | Write the whole block, write only the lines, or dress every plain code element in a page. |
| codeLines() · codeHighlight() · codeTokens() · codeLang() | function | pure | The text as lines, as lit lines, as tokens, and the language a name means. |

## Markup

```html
<!-- A figure, so the file name is its caption. The lines scroll, so they are a labelled region the keyboard can reach. -->
<figure class="tui-glass tui-code-block">
  <div class="tui-code-block__bar">
    <figcaption class="tui-code-block__tab"><span>feed.mjs</span></figcaption>
    <button class="tui-key tui-key--flat tui-key--sm tui-copy-key" type="button" data-copy-from="#cb-1" aria-label="Copy feed.mjs"></button>
  </div>
  <div class="tui-code-block__scroll" tabindex="0" role="region" aria-label="feed.mjs, code">
    <pre class="tui-code-block__pre"><code class="tui-code-block__code" id="cb-1" data-lang="js">import { connect } from '@northwind/cli';
const feed = await connect({ site: 'quay-street' });</code></pre>
  </div>
</figure>

<!-- No file name: a language legend in its place, <figcaption class="tui-label tui-code-block__lang">Shell</figcaption>.
     Start the count elsewhere with style="--cb-start:24"; mark a line with data-mark="24,25" on the <code>. -->
```

```js
import { codeBlockHTML } from '/kit/kit.mjs';

// Write the whole block from a string:
host.innerHTML = codeBlockHTML({ code, file: 'feed.mjs', lang: 'js', mark: [4] });

// Or write plain text in <code data-lang="js"> and kit.mjs splits it into lines and lights it.
```

The demo on the page:

```html
<div style="width:min(600px,calc(100vw - 96px));text-align:left"><figure class="tui-glass tui-code-block"><div class="tui-code-block__bar"><figcaption class="tui-code-block__tab"><span>feed.mjs</span></figcaption><button class="tui-key tui-key--flat tui-key--sm tui-copy-key" type="button" data-copy-from="#cb2" aria-label="Copy feed.mjs"></button></div><div class="tui-code-block__scroll" tabindex="0" role="region" aria-label="feed.mjs, code"><pre class="tui-code-block__pre"><code class="tui-code-block__code" id="cb2" data-lang="js" data-lit><span class="tui-code-block__line"><span class="tui-tok-k">import</span> { connect } <span class="tui-tok-k">from</span> <span class="tui-tok-s">'@northwind/cli'</span>;
</span><span class="tui-code-block__line">
</span><span class="tui-code-block__line"><span class="tui-tok-c">// Open the harbour feed and log each berth change.</span>
</span><span class="tui-code-block__line" data-mark><span class="tui-tok-k">const</span> feed = <span class="tui-tok-k">await</span> connect({ site: <span class="tui-tok-s">'quay-street'</span>, retries: <span class="tui-tok-n">3</span> });
</span><span class="tui-code-block__line">feed.on(<span class="tui-tok-s">'berth'</span>, (change) =&gt; {
</span><span class="tui-code-block__line">  console.log(<span class="tui-tok-s">'Berth '</span> + change.id + <span class="tui-tok-s">' is now '</span> + change.state);
</span><span class="tui-code-block__line">});
</span></code></pre></div></figure></div>
```

## Usage notes

Press Copy and paste somewhere: the text comes without its numbers. Select a few lines with the pointer: the numbers are not in the selection. Scroll the long line sideways with the pointer or, with the keyboard, Tab to the lines and press the arrow keys: the gutter stays in place.

## Accessibility

- The line numbers are drawn by the stylesheet with an empty alternative text, so a screen reader reads the code and not a count down the side, and copying or selecting the code never picks them up. The code itself is real text in a real `<code>` element, so nothing is lost to find in the page.
- The scrolling region is a stop of its own with a name, so a keyboard user can reach it and scroll long lines with the arrow keys. It has a visible 2px ring in the display’s own light.
- Lit tokens are never the only difference: a keyword is bold, a comment is italic and dimmer, and every strength is above 4.5:1 on the glass in every finish. A marked line has a bar at its edge as well as a lift of light.
- The copy key is the kit’s: it says Copied aloud, and when the browser refuses the clipboard it selects the text for the keyboard instead. The file name is the figure’s caption, so the block is named.
- On a phone the key is 44px, long lines scroll inside the block and the page itself never scrolls sideways.

## How it’s built

It is `.tui-glass`, so the glass, its scan lines, its ink and its focus light are the kit’s in every finish and volume. The gutter is a CSS counter on each line, drawn as a sticky plate over the lines that pass under it, with an engraved edge. The highlighter is a single pattern in `code-block.mjs`: it is not a parser, and a line it misreads is still the right text.

## Rules

Show code the way a lit display shows it: a file name tab, numbered lines in a gutter that holds still, a copy key, and long lines that scroll instead of wrapping.

The full rules are in `code-block.rules.md` in the brain, which ships with the Kit.

## Related parts

- [Glass](https://tactileui.dev/parts/glass): Dark display glass for plots and live readouts, whatever the finish around it. Everything printed inside it is lit.
- [Copy key](https://tactileui.dev/parts/copy-key): A key that copies a value and says so. Its lamp sits dark in the cap; when the text is on the clipboard the lamp lights, winks once, and the legend says Copied before it goes back to Copy. If the browser refuses the clipboard, the text is selected for the keyboard and the lamp lights amber.
- [Kbd](https://tactileui.dev/parts/kbd): The key to press, drawn as the key: a small cap on a skirt with a dish worked into its top. Shortcuts come as combos and follow the platform, so ⌘ K on a Mac is Ctrl K elsewhere.
