Code block
figure.code
Code block — a block of source code, with an optional filename header and optional line numbers.
Examples
@layer ui.components {
.btn {
--btn-bg: var(--color-primary);
--btn-color: var(--color-on-primary);
display: inline-flex;
align-items: center;
gap: var(--btn-gap);
min-block-size: 2.5rem;
padding-inline: var(--btn-padding-inline);
color: var(--btn-color);
background-color: var(--btn-bg);
border-radius: var(--radius-field);
}
.btn:focus-visible {
outline: var(--focus-ring);
outline-offset: var(--focus-ring-offset);
}
}<figure class="code">
<figcaption class="code-name" id="code-btn">src/css/components/button.css</figcaption>
<pre class="code-body" tabindex="0" role="region" aria-labelledby="code-btn"><code>/* the highlighter puts its spans in here */</code></pre>
</figure>Syntax highlighting is not this component’s job, and that is why the other examples on this page have none. A code block is a surface: a background, a family, a scroll container, a filename.
What colours the tokens inside it is a highlighter — Shiki, Prism, Highlight.js, a server — and every one of them emits its own <span>s. This preview is the docs being that consumer: Astro runs Shiki at build time with defaultColor={false}, which writes --shiki-light and --shiki-dark on each span and no color at all, and one rule in docs.css picks the one matching the current mode. The site therefore ships no tokeniser, no runtime and no extra request. The rule a highlighting theme has to follow is in this component’s docblock and it is one line: colour your tokens, leave the surface alone — no background, no font-family, no padding. A theme that repaints the box is a theme that undoes the component.
@layer ui.reset, ui.tokens, ui.base, ui.layout, ui.components, ui.blocks, ui.utilities;
<figure class="code">
<figcaption class="code-name" id="code-layers">src/css/layers.css</figcaption>
<pre class="code-body" tabindex="0" role="region" aria-labelledby="code-layers"><code>@layer ui.reset, ui.tokens, ui.base, ui.layout, ui.components, ui.blocks, ui.utilities;</code></pre>
</figure>The <pre> is focusable and named for the same reason the table wrapper is: code must not wrap, so a long line scrolls, and a scroll container that is not focusable is unreachable without a pointer.
npm install mostlycss
:root { --space-4: 1rem; --radius-field: 0.5rem;}
<figure class="code">
<pre class="code-body" tabindex="0" role="region" aria-label="Installing from npm"><code>npm install mostlycss</code></pre>
</figure>
<figure class="code code-numbered">
<figcaption class="code-name" id="code-num">tokens.css</figcaption>
<pre class="code-body" tabindex="0" role="region" aria-labelledby="code-num"><code><span class="code-line">:root {</span><span class="code-line"> --space-4: 1rem;</span><span class="code-line"> --radius-field: 0.5rem;</span><span class="code-line">}</span></code></pre>
</figure>No caption, so the region is named directly. Keep the tabindex even on a short snippet — whether a line overflows depends on the reader’s window, not on yours.
The numbers are counter-increment on .code-line, drawn in a ::before that is user-select: none, so copying the block copies the code and not the numbers.
npm install mostlycss
npm uninstall mostlycss
<figure class="code">
<figcaption class="code-name" id="code-install">Terminal</figcaption>
<div class="code-actions">
<button class="btn btn-sm btn-ghost" type="button" data-ui="copy" data-copy-from="#snippet-install">Copy</button>
</div>
<pre class="code-body" id="snippet-install" tabindex="0" role="region" aria-labelledby="code-install"><code>npm install mostlycss</code></pre>
</figure>
<figure class="code">
<div class="code-actions">
<button class="btn btn-sm btn-outline" type="button" data-ui="copy" data-copy-from="#snippet-remove">Copy</button>
</div>
<pre class="code-body" id="snippet-remove" tabindex="0" role="region" aria-label="Removing the package"><code>npm uninstall mostlycss</code></pre>
</figure>The slot says where, never what: copying is copy, with its permission prompt and its failure path.
With a .code-actions child the figure becomes a two-column grid, so the slot is as wide as whatever is in it and as tall — nothing is guessed. It reserves rather than overlays, and that is the repair: an absolutely positioned button in the corner was what this site had, another absolutely positioned strip landed on the same pixels with z-index: 1, and the copy button was unclickable on every example on the site. A slot in normal flow cannot be covered by something a page parks nearby.
Tokens 25
Level 2, declared on .code itself. Set any of them on that selector to restyle this component without touching the skin.
| Token | Default |
|---|---|
| --code-actions-gap | var(--space-2) |
| --code-actions-padding | var(--space-2) |
| --code-gutter-color | var(--color-on-surface-muted) |
| --code-gutter-gap | var(--space-4) |
| --code-gutter-size | 2.5ch |
| --code-inline-bg | var(--color-surface-sunken) |
| --code-inline-color | inherit |
| --code-inline-radius | var(--radius-sm) |
| --code-name-bg | var(--color-surface) |
| --code-name-color | var(--color-on-surface-muted) |
| --code-name-font-size | var(--font-size-xs) |
| --code-name-padding-block | var(--space-2) |
| --code-font-family | var(--font-mono) |
| --code-padding-block | var(--space-4) |
| --code-padding-block-end | var(--code-padding-block) |
| --code-padding-block-start | var(--code-padding-block) |
| --code-padding-inline | var(--space-4) |
| --code-bg | var(--color-surface-sunken) |
| --code-border-color | var(--color-border-subtle) |
| --code-border-width | var(--border-width) |
| --code-color | var(--color-on-surface) |
| --code-radius | var(--radius-box) |
| --code-font-size | var(--font-size-xs) |
| --code-line-height | var(--line-height-normal) |
| --code-tab-size | 2 |
Variants and states
Variants
.code-numbered.code-sm
Inside it
.code-body.code-actions.code-name.code-inline.code-line.code-wrap
State it reads
Read from the platform, never mirrored into a class that could disagree with it.
:focus
Before you ship it
What you have to do 1 requirements
- Name the region.
aria-labelledbypointing at the.code-nameis the best version, because the name is then on screen too;aria-labelis correct when there is no filename. An unnamedrole="region"is dropped from the landmarks list entirely, which puts you back where you started.
src/css/components/code-block.css · npx mostlycss add code-block