Code block

figure.code

Code block — a block of source code, with an optional filename header and optional line numbers.

Examples

Highlighted, by whatever highlights your code

View “Highlighted, by whatever highlights your code” as
@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.

With a filename

View “With a filename” as
src/css/layers.css
@layer ui.reset, ui.tokens, ui.base, 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.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.

Without one, and with line numbers

View “Without one, and with line numbers” as
npm install mostlycss
tokens.css
: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.

Tokens 21

Level 2, declared on .code itself. Set any of them on that selector to restyle this component without touching the skin.

Level 2 tokens declared by code-block
TokenDefault
--code-gutter-colorvar(--color-on-surface-muted)
--code-gutter-gapvar(--space-4)
--code-gutter-size2.5ch
--code-inline-bgvar(--color-surface-sunken)
--code-inline-colorinherit
--code-inline-radiusvar(--radius-sm)
--code-name-bgvar(--color-surface)
--code-name-colorvar(--color-on-surface-muted)
--code-name-font-sizevar(--font-size-xs)
--code-name-padding-blockvar(--space-2)
--code-bgvar(--color-surface-sunken)
--code-border-colorvar(--color-border-subtle)
--code-border-widthvar(--border-width)
--code-colorvar(--color-on-surface)
--code-font-familyvar(--font-mono)
--code-padding-blockvar(--space-4)
--code-padding-inlinevar(--space-4)
--code-radiusvar(--radius-box)
--code-font-sizevar(--font-size-xs)
--code-line-heightvar(--line-height-normal)
--code-tab-size2

Variants and states

Variants

  • .code-numbered
  • .code-sm

Inside it

  • .code-body
  • .code-line
  • .code-inline
  • .code-name
  • .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

  1. Name the region. aria-labelledby pointing at the .code-name is the best version, because the name is then on screen too; aria-label is correct when there is no filename. An unnamed role="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