Designer view
Code Block
A monospace surface for presenting source code, terminal output, or any verbatim technical content. The block variant carries chrome — language label, copy button, optional line numbers — around the `<code>` element; the inline variant renders bare `<code>` inside flowing prose for short technical references. Syntax-highlighting spans are decorative (`aria-hidden="true"`) so SR users hear the raw code text; line numbers render via CSS `counter-increment` so they never enter the clipboard; the copy button has an explicit accessible name and announces success via an `aria-live` region.
Also called Code snippet Code sample Code
When to use
Use
For verbatim technical content where character-by-character accuracy matters — source code, terminal output, configuration files, command-line invocations. Block variant for multi-line snippets where users may copy the entire content; inline variant for short technical references inside prose ("use `npm install`" or "the `aria-label` attribute"). Pair block variant with documentation surfaces (API references, tutorials), AI-surfaces (LLM responses with example code), admin panels (configuration display), and changelog surfaces (showing diff snippets).
Avoid
For non-verbatim text styled to look like code — that is typography work, not Code Block. For interactive code editors (Monaco, CodeMirror, Sandpack) — those are dedicated editor components with cursor / selection / autocomplete semantics out of scope here. For inline-code-explanation hover hosts — that is `Tooltip` anchored to inline `<code>`. For diff-display with collapsible per-file sections — escalate to a dedicated diff component, not a stretched Code Block. For ASCII-art or pre-formatted text that is not code, use `<pre>` directly without the chrome.
Versus related
- tooltip
`Tooltip` hosts a short on-hover / on-focus description anchored to a trigger; `Code Block` is a standalone surface displaying verbatim code. Tooltip on inline `<code>` is the canonical pattern for explaining what a code reference means without taking users out of the flowing prose; Code Block is the surface where the actual code lives. The two pair across documentation contexts — Code Block carries the example, Tooltip carries the per-token explanation when authored.
Code Block is the canonical surface for verbatim technical content — source code in documentation, command output in tutorials, configuration snippets in admin panels, AI-surface responses with example code. The reference documents the `<pre><code>` semantic, the syntax-highlight-as-decorative contract, the line-numbers-not-in-clipboard rule (CSS counters), the copy-button accessible-name + `aria-live` feedback contract, and the boundary with `Tooltip` (which hosts inline-code- explanations on hover but never replaces the code-block surface itself).
Figma anatomy
| Slot | Figma type | Hint |
|---|---|---|
root | frame | Code-block frame; variant property switches between block (pre+chrome) and inline (bare code) shapes |
code | text | Monospace text; preserved whitespace; tokens visualized via syntax-highlight palette |
language-label | text | Language label text style; uppercase or small-caps; positioned in the chrome bar |
copy-button | instance | Button instance with copy / copied / error icon variants; default / hover / focus-visible / pressed states |
line-numbers | frame | Decorative numerical gutter; muted color; right-aligned within fixed-width column |
line | instance | Line container instance; default / highlighted / diff-add / diff-remove variants |
token | text | Per-token span; color-coded per syntax-highlight palette |
status-announcement | text | Visually-hidden text element for copy feedback |
Token usage per slot
code- typography
- size
text.sm
- size
language-label- color
- foreground
color.text.muted
- foreground
- typography
- size
text.xs - weight
weight.medium - tracking
tracking.wide
- size
line-numbers- color
- foreground
color.text.muted
- foreground
- typography
- size
text.xs
- size
Figma ↔ Code property map
| Figma | Kind | Code | Notes |
|---|---|---|---|
Variant | Enum | variant | Maps block / inline structural shapes. |
Size | Enum | size | sm / md. |
Language | Text | language | Language identifier (e.g., javascript / typescript / python / bash). Drives highlighter invocation and language-label content. |
Show Line Numbers | Boolean | showLineNumbers | Block variant only. Renders line-numbers slot via CSS counter-increment. |
Show Copy Button | Boolean | showCopyButton | Block variant only. Renders copy-button slot. |
Show Language Label | Boolean | showLanguageLabel | Block variant only. Renders language-label slot. |
Wrap | Boolean | wrap | When true, long lines wrap to next line. When false (default), long lines overflow horizontally and the root becomes a focusable scrollable-region. |
Code | Slot | code | The verbatim code text (slotted children for inline; string-prop or children for block). |
Copy Label | Text | copyLabel | Accessible name for the copy-button (default "Copy code"; translated per locale). |
Motion
| Transition | Duration token |
|---|---|
copyFeedback | motion.duration.fast |
Responsive behaviour
| Breakpoint | Change |
|---|---|
breakpoint.sm | Below this width, block variant either horizontally scrolls (default — wrap=false) or wraps long lines (wrap=true). The language- label and copy-button may stack into a single compact chrome row or collapse the language-label entirely on very narrow viewports. Inline variant flows with surrounding prose at its native size. |
breakpoint.md | At and above this width, block variant renders with full chrome (language-label, copy-button, optional line-numbers) and the canonical horizontal padding. Long lines scroll horizontally inside the focusable region; wrap behaviour follows the `wrap` property. |
Internationalisation
RTL · mirroring
Code text (the `<code>` element's content) is always rendered LTR regardless of document direction — code is a left-to-right writing system universally (programming languages use Latin script and ASCII operators). Set `dir="ltr"` explicitly on the `<code>` element when the surrounding document is RTL, otherwise bidi may misalign mixed content. The chrome (language-label, copy-button) follows document direction — copy-button sits at inline-end, language- label at inline-start. Line-numbers gutter stays at inline-start of the code text (visually right of the code under RTL context).
Text expansion
Language label expands minimally under translation — language names like "JavaScript", "TypeScript", "Python" are stable across locales. Copy-button accessible name expands ("Copy code" → "Code kopieren" 35% longer; "Copier le code" 25% longer); reserve flexible inline- size in the button. Live-region status text expands similarly. Code text itself is not translated — code is a universal language.
Variants, properties, states
Variants
Structurally different versions of the component.
block inline Properties
The same component, parameterised.
| Property | Type |
|---|---|
size | sm | md |
showLineNumbers | boolean |
showCopyButton | boolean |
showLanguageLabel | boolean |
wrap | boolean |
States
Browser/user-driven (interactive) vs. app-driven (data).
| Kind | States |
|---|---|
interactive | hoverfocus-visibleactive |
data | idlecopyingcopiederror |
State transitions
| From | To | Trigger |
|---|---|---|
idle | copying | User activates the copy-button |
copying | copied | `navigator.clipboard.writeText` resolves |
copying | error | `navigator.clipboard.writeText` rejects (permission denied, insecure context) |
copied | idle | Auto-reset after ~2 seconds returns the copy-button to its default state |
Figma↔Code mismatches
- 01 Figma
Syntax highlighting drawn as colored text inline
Code`<span aria-hidden="true">` wrappers around each token
ConsequenceDesigners compose code blocks with inline color per token (keywords red, strings green, comments grey); developers wrap each token in `<span>` for the coloring. Without explicit anatomy, the spans may ship without `aria-hidden` and SR users hear "keyword function, identifier myFunc, punctuation open paren, parameter x, ..." instead of the actual code. The visible result looks identical; the AT result is unusable.
CorrectDocument syntax-highlight spans as decorative slots with `aria-hidden="true"`. Figma carries syntax- highlight palette as design-tokens but the per- token wrappers are noted as decorative — the design surface signals "color is presentation only". Code wraps tokens in `<span class="token-keyword" aria-hidden="true">` or uses a highlighter library (Shiki) that emits aria-hidden by default.
- 02 Figma
Line numbers drawn as text in left gutter
CodeCSS `counter-increment(line)` rendering numbers as `::before` content
ConsequenceDesigners draw line numbers as inline text in a left-side column; developers ship them as either text-children (which enter the clipboard when users copy the code) or as CSS counters (which do not). Without explicit anatomy, the text-as- children pattern is the default and users copy-paste "1\\n2\\n3\\nconst x = 1;" instead of "const x = 1;". Subtle regression — most users never notice but technical users hit it immediately.
CorrectDocument line-numbers as a decorative slot rendered via CSS `counter-increment` so the numbers exist style-side and not text-side. Figma carries the line-numbers as a frame-property treatment (visual gutter) not as inline text. Code uses `pre code { counter-reset: line }` plus `pre code > .line::before { counter-increment: line; content: counter(line) }` so the numbers render visually but never enter the textContent.
- 03 Figma
Copy button drawn as icon-only without label
Code`<button aria-label="Copy code">` plus `aria-live` feedback region
ConsequenceDesigners ship the copy button as an icon-only button (clipboard glyph) for visual cleanliness; developers must add `aria-label` so SR users hear what the button does. Without explicit anatomy, the design file does not communicate the label-required contract and implementations may ship icon-only buttons without aria-label — invisible to AT.
CorrectDocument the copy-button slot with an explicit accessible-name requirement. Figma carries an "Aria Label" component property (e.g., default "Copy code") so the design surface signals the contract. Code wires `<button aria-label="Copy code">` plus a sibling `<span aria-live="polite">` for success / failure feedback. Build-time lint enforces that all icon-only Code Block copy buttons have aria-label set.
- 04 Figma
Language label drawn as a static visible badge
Code`<span>` with optional `aria-hidden` based on language-name redundancy
ConsequenceDesigners compose the language label as a visible pill / badge in the chrome; developers ship it as `<span>` with the language name. Without anatomy guidance, the span may carry no `aria-hidden` — SR users hear the language name in addition to the code's textContent, which can be valuable (multi-language documentation) or noisy (when the language is obvious from context). The decision is contextual; canonical anatomy documents both patterns.
CorrectDocument the language-label slot with two patterns — visible-and-announced (default, `<span>` without aria-hidden, useful for multi-language docs) and visible-only (`<span aria-hidden="true">`, useful when the language is obvious). The `aria-hidden` is the author's choice based on context; the anatomy surfaces both as canonical.
- 05 Figma
Inline code drawn as styled text inside paragraph
Code`<code>` element with monospace font and inline-flow display
ConsequenceDesigners compose inline code references with a monospace font run inside paragraph text; developers ship `<code>` elements which SR announces as "code, npm install, code" (entering and exiting the code semantic). Without explicit anatomy, the semantic boundary is invisible in the design file — sighted users see styled text, AT users hear the code-vs-prose distinction. Both surfaces are correct but the anatomy ensures developers ship `<code>` and not a styled `<span>`.
CorrectDocument inline as a structural variant of Code Block with `<code>` as the root. Figma carries an "inline" variant of the Code Block component for use inside Text components. Code ships `<code>` (not `<span>`) so SR users hear the semantic boundary. The visual styling (monospace font, subtle background pill) is common to both surfaces; the semantic is what the anatomy documents.
Contracts
Non-negotiable contracts
WCAGWCAG 1.3.1 Info and Relationships The code's `textContent` matches the verbatim code that should be copyable. Syntax-highlight wrappers are decorative — `aria-hidden="true"` — so SR users hear the raw code, not the token roles. Implementations that ship without aria-hidden on syntax spans pollute the AT announcement.
Without the rule, SR users hear "keyword function, identifier myFunc, punctuation open paren, parameter x, ..." instead of "function myFunc(x)" and cannot follow the code. The token-as-decoration semantic is the canonical assistive-tech contract for syntax- highlighted code.
WCAGWCAG 4.1.2 Name, Role, Value The copy-button has an explicit accessible name (`aria-label="Copy code"` or visible text label) AND surfaces success / failure feedback via both visible icon transition and an `aria-live="polite"` region.
Without the rule, icon-only copy-buttons announce as "button" with no semantic; clipboard writes occur silently and users cannot tell whether the copy succeeded. AT users + first-time users both lose trust in the affordance.
Canon Line numbers render via CSS `counter-increment` (or as an `aria-hidden` sibling gutter) so they do NOT enter the clipboard when users copy the code. Implementations that ship line numbers as text-children pollute every paste.
Without the rule, users copy "1\\nconst x = 1;\\n2\\nconst y = 2;" instead of "const x = 1;\\nconst y = 2;" and have to manually strip line numbers in their editor. Functional regression that erodes trust in the documentation surface. The CSS-counter pattern is the canonical clean-clipboard discipline.
WCAGWCAG 2.1.1 Keyboard Block variant with horizontal overflow wraps the code in a focusable scrollable region — `tabindex="0"` plus `role="region"` plus accessible name. Keyboard users discover and scroll the area via arrow keys.
Without the rule, long code lines (function signatures, URL strings) become invisible to keyboard users — Tab skips past, arrow keys do nothing. Sighted-mouse users scroll fine; the affordance is invisible to keyboard and AT. WCAG 2.1.1 violation.
HTML specHTML standard — pre and code element semantics Block variant uses `<pre>` and inline variant uses `<code>` — the canonical HTML semantics for preformatted-text and inline-code. Implementations that ship `<div>` with monospace styling lose the AT-announced boundary between code and prose.
Without the rule, SR users navigating prose do not hear the code-vs-prose boundary — inline code references blend into surrounding text with no semantic signal. Block code ships without the preformatted-text announcement. The HTML semantics are canonical and free; substituting them for styled `<div>`s is regression.
Vocabulary drift
- HTML
<pre> + <code>- Native HTML elements. `<pre>` for block- level preformatted text (preserves whitespace + monospace); `<code>` for inline code references. The canonical anatomy mirrors this baseline.
- WAI-ARIA
— (no formal Code role)- WAI-ARIA does not ship a Code role because `<pre>` and `<code>` carry sufficient native semantics. The canonical anatomy uses the HTML elements directly; ARIA roles are only required when the structural elements cannot be authored (rare).
- Material 3
— (no formal Code Block component)- Material 3 spec does not include Code Block. Material UI ships no Code Block primitive. Material-based documentation sites compose custom Code Block components from the highlighter library (Prism, Shiki) of choice. Explicit absence is canonical for Material.
- Carbon
CodeSnippet (single + multi + inline + terminal)- Carbon ships `CodeSnippet` with four variants — single-line, multi-line, inline, terminal. The canonical block / inline variants cover Carbon's single + multi + inline; Carbon's terminal variant adds prompt-symbol prefix handling, which the canonical anatomy does not promote to a first-class variant (terminal is a styling concern, not structural).
- Atlassian
Code (block) + Code (inline)- Atlassian ships block and inline variants as separate components named `Code` (with a `language` prop for block). Naming-only divergence; the canonical single-component- with-variants matches Atlassian's functional surface.
- GitHub Primer
CodeBlock (Markdown-rendered)- Primer renders code blocks via the GitHub- Markdown pipeline — `<pre><code>` with per-token `<span>` syntax-highlight wrappers (server-rendered via Linguist). Inline `<code>` is bare. The canonical anatomy mirrors this approach; clipboard-and-language-label chrome is added on top for documentation surfaces.
- Shiki
Server-rendered HTML via Shiki highlighter- Shiki is the modern canonical choice for SSR syntax highlighting (used by Vitepress, Astro, Docusaurus). Emits `aria-hidden="true"` on syntax-highlight spans by default — canonical-compliant out of the box. Token wrappers carry inline styles directly so the rendered HTML is portable across themes.
- Prism
Client-side highlighter library- Prism does not emit `aria-hidden` on token wrappers by default. Implementations using Prism must add the `aria-hidden` wrapper themselves OR migrate to Shiki for canonical-compliant output. Prism remains common in older React-based documentation sites; the canonical contract requires the author-side wrapper.
Common mistakes
#code-block-copy-no-label
Copy button is icon-only without `aria-label`
The copy-button ships as an icon-only `<button>` (clipboard glyph) without an accessible name. SR users hear "button" with no indication of what activating it does — and after activation, no indication that the copy succeeded. WCAG 4.1.2 Name-Role-Value violation.
Set `aria-label="Copy code"` (or the locale- translated equivalent) on the button. For success feedback, update an `aria-live="polite"` sibling region with "Copied to clipboard" (clear after ~3 seconds). Visible icon may flip to a checkmark for sighted feedback; the live-region carries the AT-equivalent.
#code-block-line-numbers-in-copy
Line numbers enter the clipboard when users copy code
Line numbers are rendered as text-children inside the `<pre>` element rather than via CSS counters. When users select-all + copy or use the copy- button, the clipboard receives "1\\nconst x = 1;\\n2\\nconst y = 2;" instead of "const x = 1;\\nconst y = 2;". Users paste broken code into their editor and have to manually strip the numbers. Functional regression that erodes trust in the documentation.
Render line numbers via CSS `counter-increment` so the numbers exist in style-side, not text- side. The clipboard receives the code text only. For copy-button implementations, copy `code.textContent` (which excludes `::before` pseudo-element content) explicitly rather than `pre.textContent`.
#code-block-copy-no-feedback
Copy button activation produces no visible or AT feedback
Activating the copy-button silently writes to the clipboard with no visible state change and no `aria-live` announcement. Users wonder whether the copy succeeded (slow clipboard, browser permission prompt, insecure context rejection); they activate the button repeatedly to "make sure", potentially overwriting clipboard content they wanted. Trust regression.
On copy-success, transition the visible button icon to a checkmark for ~2 seconds AND update an `aria-live="polite"` region with "Copied to clipboard". On copy-error (permission denied, insecure context), transition to an error icon AND announce "Copy failed". The dual-channel feedback (visible + AT) is canonical.
#code-block-pre-overflow-not-focusable
Long horizontal-overflowing code is not keyboard-scrollable
Block variant ships with `overflow-x: auto` on the `<pre>` but no `tabindex="0"` and no `role="region"`. Sighted-mouse users scroll fine; keyboard users cannot reach the scrolled content — Tab skips past, arrow keys do nothing. Long code lines (function signatures, URL strings) become invisible to keyboard users.
Add `tabindex="0"` plus `role="region"` plus `aria-label="${language} code, scrollable"` to the `<pre>` whenever horizontal overflow is possible. Visible focus ring on focus-visible signals the scrollable boundary. Alternative — the `wrap` property wraps long lines so no overflow occurs (canonical for prose-heavy documentation; not for code where line breaks change semantic).
Accessibility hints
| Slot | Accessibility hint | |
|---|---|---|
root | Block variant: `<pre>` carries the preserved-whitespace semantic and is announced as "preformatted text" or "code block" by SR. Inline variant: `<code>` inside prose flow is announced inline as "code text". Do not substitute `<div>` with monospace styling for either — `<pre>` and `<code>` carry semantics that AT relies on for code-vs-prose distinction. | |
code | Plain text content is the AT-readable surface — keep the textContent matching the verbatim code that should be copyable. Syntax-highlight wrappers (`<span>` per token) wrap the same text but carry `aria-hidden="true"` so SR users hear the raw code, not "keyword, function, name, string". The `lang` attribute (`<code lang="javascript">`) is optional but helpful for mixed-language documentation. | |
language-label | Render as `<span id="code-block-N-language">` and let SR users hear it via reading order. For block variants without a visible label but where the language is known, expose it via `aria-label` on the root or as sr-only text inside the chrome. Avoid making the label a focusable element — language identification is metadata, not action. | |
copy-button | Accessible name "Copy code" or "Copy code to clipboard" — never icon-only without `aria-label`. On activation, the button's visible icon flips to the copied state (checkmark) and the status-announcement slot updates with "Copied to clipboard". Some implementations replace the button's accessible name temporarily ("Copied"); the canonical pattern keeps the name stable and uses the live-region for transient confirmation. Visible focus ring per WCAG 2.4.7. | |
line-numbers | Render via CSS `counter-increment(line)` plus `::before { content: counter(line) }` so the numbers exist in style-side, not text-side. The clipboard receives the code text only — line numbers stay out. Alternative implementation: an `aria-hidden="true"` `<span>` gutter where each line number is text but the SR + clipboard skip them. Prefer CSS counters because they are robust to copy-via-keyboard-shortcut and copy-via-context- menu equally. | |
line | The wrapper itself is decorative; the line's text content is what SR reads. For diff lines, encode the add/remove semantic via visible text symbol (+/-) at the start of the line so SR users hear the change-direction, not just the styling. Color- only diff signaling violates WCAG 1.4.1 Use of Color. | |
token | `aria-hidden="true"` on each token wrapper — critical because SR users would otherwise hear "keyword function, identifier myFunc, punctuation open paren, ..." instead of the actual code. Highlighter libraries that emit aria-hidden by default (Shiki) are canonical; libraries that do not (Prism unless configured) require an author-side wrapper. The text content inside tokens IS read; only the wrapper announcement is suppressed. | |
status-announcement | `<span aria-live="polite" class="sr-only">` whose text updates to "Copied to clipboard" on success and "Copy failed" on error. Use `aria-live="polite"` not "assertive" — the user just initiated the copy; assertive interruption is rude. Clear the text after ~3 seconds so the next copy announcement is announced as a change. |