Code in a well, colored by kind and numbered by line, with a top bar for a file name or language tabs and small icon actions such as copy.
.pricing-card {
padding: var(--smbk-space-inset-lg);
border-radius: var(--smbk-radius-container-md);
background: var(--smbk-color-surface-raised);
}Options
| Choice | Values | Default | What it does |
|---|---|---|---|
baresmbk-code--bare | On or off | Off | No top bar: just the well. |
Examples
Usage
A file name over the code, each kind of code in its color, with a copy action.
.pricing-card {
padding: var(--smbk-space-inset-lg);
border-radius: var(--smbk-radius-container-md);
background: var(--smbk-color-surface-raised);
}With tabs
Tabs in the bar switch between commands; with tabs the bar shows no file name. One line shows no number.
npm install simbolikTabs for formats
Tabs switch the same thing between formats.
.pricing-card {
padding: var(--smbk-space-inset-lg);
border-radius: var(--smbk-radius-container-md);
background: var(--smbk-color-surface-raised);
}React
React and TypeScript: keywords, names, attributes and text in their colors.
import { Button } from 'simbolik';
// Starts the plan the reader chose.
export function PlanButton({ plan }: { plan: string }) {
return <Button tone="brand" onClick={() => choose(plan, 12)}>Choose {plan}</Button>;
}Markup
HTML: tags, attributes and their values.
<!-- The plan card -->
<article class="smbk-card">
<h3 class="smbk-card__title">Studio</h3>
<a class="smbk-btn smbk-btn--tone-brand" href="/pricing">Choose Studio</a>
</article>Json
JSON: keys, text and values.
{
"name": "pricing-site",
"private": true,
"dependencies": { "simbolik": "0.2.0" },
"simbolik": { "prefix": "site-" }
}Guidelines
Purpose
Code shown as code: monospace, preserved whitespace, each kind of code in its own color and each line numbered, in a well, with a bar above it for what the code is (a file name, or tabs for alternatives such as package managers) and small actions such as copy.
When to use
- Any code or command a reader may copy: installation lines, snippets, configuration.
- The Showroom's own snippets.
When not to use
- Inline code in a sentence. Use the code element with the mono face.
- Output that arrives line by line as a program runs. Use a log.
Usage rules
Do
- Give a copy action to any snippet meant to be pasted.
- Name the bar label after the file or the language.
- Say the language, so each kind of code takes its color.
Don't
- Put anything but code in the well. Comments belong in prose above it.
.pricing-card {
padding: var(--smbk-space-inset-lg);
border-radius: var(--smbk-radius-container-md);
background: var(--smbk-color-surface-raised);
}Name the bar after the file or the language, so the reader knows where the code goes.
.pricing-card {
padding: var(--smbk-space-inset-lg);
border-radius: var(--smbk-radius-container-md);
background: var(--smbk-color-surface-raised);
}A bar that just says "Code" tells the reader nothing the well does not already show.
.pricing-card {
padding: var(--smbk-space-inset-lg);
border-radius: var(--smbk-radius-container-md);
background: var(--smbk-color-surface-raised);
}Explain the snippet in the text above and keep only code in the well, so it can be copied as it is.
Paste this into your stylesheet and raise the padding if you need more room.
.pricing-card {
padding: var(--smbk-space-inset-lg);
border-radius: var(--smbk-radius-container-md);
background: var(--smbk-color-surface-raised);
}Instructions inside the well get copied along with the code and break it.
Anatomy
- The block, a bordered container.
- The bar part, holding the tabs part or the label part at the start and the actions part at the end. With tabs the bar shows no label: the tabs name the code.
- The label part, the label component's subtle look: a file icon, the icon part, then the file name.
- The action part, a small icon button in a 24 px square inside the actions, filled like a ghost button while hovered or pressed, and faint when it cannot be used.
- The body part, the well.
- The numbers part, the line numbers in a column before the code, quieter than the code.
- The code part, the preformatted text in the code style, the mono face, always left to right.
- A part for each kind of code it colors: keyword, declaration, string, number, function, type, tag, property, comment and punctuation, each in its code color.
- The status part, a status line out of sight, in a block with actions.
Behavior
Given its language, the block colors each kind of code, in the manner of a familiar code editor: words that steer (import, return, if) in purple, words that declare (const, function, class) and tags and selectors in blue, text in quotes in orange, numbers in green, function names in yellow, types and components in teal, properties and keys in light blue, and comments and punctuation in gray. It reads CSS, HTML, JavaScript and TypeScript with or without JSX, JSON and shell commands, by their shape rather than their meaning, so an unusual line may stay one color; code in another language, or with no language given, stays one color. The language also picks the file icon before the label, or the label's file ending does. Each line is numbered unless the numbers are turned off; code of one line shows no number. The numbers are left out when the code is selected.
A page built without React colors its code the same way with the block's highlighter, beside the component, which cuts code into pieces and says each one's kind; markup written by hand puts each piece in a span with its part's class.
Tabs switch which snippet is shown; the page supplies the snippet for each. Actions call back; an action marked disabled takes no press. When the page gives an action another icon, such as a tick once its code is copied, the two icons cross-fade in the action's one place: the old one shrinks and blurs away and the new one comes in a little slower; under reduced motion they only cross-fade. The status line still says what happened. Long lines scroll sideways inside the well rather than wrapping. Code reads left to right on a right-to-left page too, its numbers on the left: markup written by hand gives the body and the code element dir="ltr".
Accessibility
What it does for you
- The code is a real preformatted code element, read as code.
- Color is never the only way to tell code apart: the code reads the same without it, and each code color stands out from the well enough to read as text in both themes.
- The line numbers are hidden from screen readers and left out of a selection, so they are never read or copied with the code.
- Each action is a button with an accessible name.
- A block with actions keeps a status line out of sight, in the page from the start. A screen reader reads it whenever its words change, where many do not read a new name on the button that has focus (WCAG 2.2, 4.1.3 Status Messages).
- Tabs are the tabs component at its small size, a tab list with a name. The body is their one panel, named by the tab that shows it, and Tab reaches it, so a long line can be scrolled with the keyboard.
- Without tabs, a body whose code runs past it is a region named by the bar's label, or "Code" when there is none, and Tab reaches it, so it can be scrolled with the keyboard. A body that fits takes no Tab.
- A bare block shows no bar, but a label given to it still names its body.
What you must do
- Put the result of an action in the status line (
status, such as "Copied"), then empty it after a moment. - Say what the tabs choose between (
tabsLabel), which is "Language" unless told otherwise. - Give blocks with bars on one page different labels, and give each bare block on a page a label, so a page of several blocks can tell them apart.
How to test
- Tab through the block: each action takes focus, then the tab list, then a body that scrolls, with the focus ring showing in both themes and in a forced-color mode. Scroll a long line with the arrow keys.
- Press an action with Enter, then Space: each runs it once, and a screen reader says the status, such as "Copied".
- With a screen reader, the code is read as code, line numbers are not read, and tabs are read as a tab list named by
tabsLabel. - Select the code and copy it: line numbers are left out.
- With text at 200% and the page 320 pixels wide, the body scrolls sideways instead of cutting the code off.
Keyboard
| Key | What it does |
|---|---|
| Tab | Moves focus through the actions, then the tab list, then the body when the body scrolls or is the tabs' panel. |
| EnterorSpace | Presses the focused action. |
| Left ArroworRight Arrow | On a tab, moves to the previous or next tab and shows its code. On a body that scrolls, scrolls it sideways. |
| Up ArroworDown Arrow | On a body that scrolls, scrolls it. |
| HomeorEnd | On a tab, moves to the first or last tab. |
Source
The files the code block is made of. Its folder on GitHub (opens in a new tab) holds them with its wrapper and its document.
Stylesheet
/* smbk-code — Code block. Figma "CodeBlock" 4255:6452 and "CodeBlockAction" 4255:6264.
* The code is set in Code MD, the mono face, always left to right, beside its line numbers, each kind of code in
* its code color. The bar names the code with a file icon and a subtle label, the label component's own, or
* switches it with tabs. Its actions are ghost icon buttons in a 24 px box. */
.smbk-code {
box-sizing: border-box;
/* Holds the status line, which is out of sight, inside the block rather than wherever the page would put it. */
position: relative;
display: flex;
flex-direction: column;
width: 100%;
overflow: hidden;
border: var(--smbk-border-width-hairline) solid var(--smbk-color-border-subtle);
border-radius: var(--smbk-radius-container-sm);
color: var(--smbk-color-text-default);
}
.smbk-code__bar {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--smbk-space-inline-lg);
/* The edge is drawn inside the bar, as Figma counts it: the padding under the row gives its width back. */
padding: var(--smbk-space-inset-md);
padding-bottom: calc(var(--smbk-space-inset-md) - var(--smbk-border-width-hairline));
border-bottom: var(--smbk-border-width-hairline) solid var(--smbk-color-border-subtle);
background: var(--smbk-color-surface-default);
}
/* The label sits on the label component's subtle look (smbk-label smbk-label--tone-subtle), which sets its text and
the gap after the icon. */
.smbk-code__label {
padding: 0 var(--smbk-space-inset-xs);
}
.smbk-code__icon {
color: var(--smbk-color-text-disabled);
font-size: var(--smbk-size-icon-sm);
}
.smbk-code__tabs {
display: inline-flex;
}
/* With tabs the body is their panel, which Tab reaches: its ring is drawn inside its edge, because the block clips what runs past it. */
.smbk-code__body:focus-visible {
outline: var(--smbk-focus-ring-width) solid var(--smbk-color-border-focus);
outline-offset: calc(-1 * var(--smbk-focus-ring-width));
}
.smbk-code__actions {
display: inline-flex;
align-items: center;
gap: var(--smbk-space-inline-xs);
}
.smbk-code__action {
box-sizing: border-box;
display: inline-grid;
place-content: center;
place-items: center;
gap: var(--smbk-space-inline-xs);
height: var(--smbk-size-control-xs);
padding: var(--smbk-space-inset-xs);
border: 0;
border-radius: var(--smbk-radius-control-sm);
background: none;
color: var(--smbk-color-text-subtle);
font-size: var(--smbk-size-icon-sm);
cursor: pointer;
appearance: none;
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
-webkit-user-select: none;
user-select: none;
}
/* Hovered and pressed, the ghost button's fills. The focus ring alone shows focus: Figma's raised fill is the
bar's own. */
@media (hover: hover) {
.smbk-code__action:hover {
background: var(--smbk-color-action-ghost-background-hover);
color: var(--smbk-color-text-default);
}
}
.smbk-code__action:active {
background: var(--smbk-color-action-ghost-background-pressed);
color: var(--smbk-color-text-default);
}
.smbk-code__action:focus-visible {
outline: var(--smbk-focus-ring-width) solid var(--smbk-color-border-focus);
outline-offset: var(--smbk-focus-ring-offset);
}
.smbk-code__action:disabled {
background: none;
color: var(--smbk-color-text-disabled);
cursor: not-allowed;
}
/* The action's icon has one cell: an icon the page swaps for another (Copy, then Check once copied) shares it with
the one it replaces while the two cross-fade (motion, below). */
.smbk-code__action > * {
grid-area: 1 / 1;
}
.smbk-code__body {
position: relative;
display: flex;
align-items: flex-start;
gap: var(--smbk-space-inline-lg);
padding: var(--smbk-space-inset-xl);
background: var(--smbk-color-surface-raised);
overflow: auto;
/* The system's scrollbar: thin, in the subtle border color, on a clear track. */
scrollbar-width: thin;
scrollbar-color: var(--smbk-color-border-subtle) transparent;
}
.smbk-code__numbers,
.smbk-code__code {
display: block;
margin: 0;
font-family: var(--smbk-typography-code-md-font-family);
font-size: var(--smbk-typography-code-md-font-size);
font-weight: var(--smbk-typography-code-md-font-weight);
line-height: var(--smbk-typography-code-md-line-height);
letter-spacing: var(--smbk-typography-code-md-letter-spacing);
text-transform: var(--smbk-typography-code-md-text-transform);
white-space: pre;
tab-size: 2;
}
/* The line numbers: a column the reader can point by, quieter than the code, left out of a selection. */
.smbk-code__numbers {
flex: none;
color: var(--smbk-color-text-disabled);
text-align: end;
-webkit-user-select: none;
user-select: none;
}
/* ---- each kind of code in its code color ---- */
.smbk-code__keyword { color: var(--smbk-color-code-keyword); }
.smbk-code__declaration { color: var(--smbk-color-code-declaration); }
.smbk-code__string { color: var(--smbk-color-code-string); }
.smbk-code__number { color: var(--smbk-color-code-number); }
.smbk-code__function { color: var(--smbk-color-code-function); }
.smbk-code__type { color: var(--smbk-color-code-type); }
.smbk-code__tag { color: var(--smbk-color-code-tag); }
.smbk-code__property { color: var(--smbk-color-code-property); }
.smbk-code__comment { color: var(--smbk-color-code-comment); }
.smbk-code__punctuation { color: var(--smbk-color-code-punctuation); }
/* The status line: what the last action did, for a screen reader only. Out of sight but in the page,
so what is put in it is read aloud. */
/* .smbk-code__status: hidden from view, kept for screen readers and the keyboard, by the shared rule in visually-hidden/visually-hidden.css. */
/* ---- motion: an action whose icon the page swaps (Copy, then Check once copied) cross-fades the two in their one
cell. The leaving icon fades out with the feedback purpose, shrinking and blurring as it goes; the arriving one
comes back from that look, slower, with the enter purpose, so it lands after the other has gone. Not the exit
purpose for the leaving one: its easing holds an icon almost whole until its last moment, so the two would show
on top of each other. The icon an action starts with simply shows. Under reduced motion the two only cross-fade. ---- */
.smbk-code__action > [data-swap] {
transition-property: opacity, scale, filter;
transition-duration: var(--smbk-motion-enter-duration);
transition-timing-function: var(--smbk-motion-enter-easing);
}
.smbk-code__action > [data-swap="out"] {
opacity: 0;
transition-duration: var(--smbk-motion-feedback-duration);
transition-timing-function: var(--smbk-motion-feedback-easing);
}
@starting-style {
.smbk-code__action > [data-swap="in"] {
opacity: 0;
}
}
@media (prefers-reduced-motion: no-preference) {
.smbk-code__action > [data-swap="out"] {
scale: 0.6;
filter: blur(var(--smbk-blur-4));
}
@starting-style {
.smbk-code__action > [data-swap="in"] {
scale: 0.6;
filter: blur(var(--smbk-blur-4));
}
}
}
/* ---- touch: each action takes an invisible press area ----
On a touch screen an invisible area, centred on each action, takes the press out to the comfortable target in each
direction it falls short of it, over the code under the bar, and between two actions only halfway into their gap;
the block cuts it off at its edge. The actions, their focus ring and their places stay as drawn. */
@media (pointer: coarse) {
.smbk-code__action {
position: relative;
}
.smbk-code__action::after {
z-index: 1;
content: '';
position: absolute;
inset: min(0%, (100% - var(--smbk-size-target-comfortable)) / 2);
}
.smbk-code__action ~ .smbk-code__action::after {
inset-inline-start: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-inline-xs) / -2);
}
.smbk-code__action:has(~ .smbk-code__action)::after {
inset-inline-end: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-inline-xs) / -2);
}
}Tokens it reads
| Token and CSS | Value | Preview | Foundation |
|---|---|---|---|
| border-width.hairline--smbk-border-width-hairline | 1px | Border width | |
| color.border.subtle--smbk-color-border-subtle | light: #c4cdd8dark: #2d3237changes with theme | Color | |
| radius.container.sm--smbk-radius-container-sm | 8px | Radius | |
| color.text.default--smbk-color-text-default | light: #232527dark: #e7ebf0changes with theme | Color | |
| space.inline.lg--smbk-space-inline-lg | comfortable: 12pxcompact: 8pxchanges with density | Spacing | |
| space.inset.md--smbk-space-inset-md | comfortable: 8pxcompact: 6pxchanges with density | Spacing | |
| color.surface.default--smbk-color-surface-default | light: #f3f5f8dark: #181a1cchanges with theme | Color | |
| space.inset.xs--smbk-space-inset-xs | 4px | Spacing | |
| color.text.disabled--smbk-color-text-disabled | light: #7c8692dark: #606a75changes with theme | Color | |
| size.icon.sm--smbk-size-icon-sm | 16px | Size | |
| focus.ring.width--smbk-focus-ring-width | 2px | Focus | |
| color.border.focus--smbk-color-border-focus | light: #cc0d0ddark: #f34336changes with theme | Color | |
| space.inline.xs--smbk-space-inline-xs | 4px | Spacing | |
| size.control.xs--smbk-size-control-xs | 24px | Size | |
| radius.control.sm--smbk-radius-control-sm | 4px | Radius | |
| color.text.subtle--smbk-color-text-subtle | light: #4e555edark: #9ba5b1changes with theme | Color | |
| color.action.ghost.background-hover--smbk-color-action-ghost-background-hover | light: #373d4326dark: #606a754dchanges with theme | Color | |
| color.action.ghost.background-pressed--smbk-color-action-ghost-background-pressed | light: #c4cdd8dark: #2d3237 | Color | |
| focus.ring.offset--smbk-focus-ring-offset | 2px | Focus | |
| space.inset.xl--smbk-space-inset-xl | comfortable: 16pxcompact: 12pxchanges with density | Spacing | |
| color.surface.raised--smbk-color-surface-raised | light: #f9fafbdark: #232527changes with theme | Color | |
| typography.code-md--smbk-typography-code-md-font-family--smbk-typography-code-md-font-size--smbk-typography-code-md-font-weight--smbk-typography-code-md-line-height--smbk-typography-code-md-letter-spacing--smbk-typography-code-md-text-transform | fontFamily: Geist Mono, Geist Mono Fallback, ui-monospace, monospacefontSize: 14pxfontWeight: 400lineHeight: 1.6letterSpacing: 0px | The quick brown fox | Typography |
| color.code.keyword--smbk-color-code-keyword | light: #9b1cefdark: #c18cf9changes with theme | Color | |
| color.code.declaration--smbk-color-code-declaration | light: #3b5ce3dark: #7fa0ffchanges with theme | Color | |
| color.code.string--smbk-color-code-string | light: #b14904dark: #fa8349changes with theme | Color | |
| color.code.number--smbk-color-code-number | light: #037a1fdark: #8ddf90changes with theme | Color | |
| color.code.function--smbk-color-code-function | light: #776906dark: #dfce64changes with theme | Color | |
| color.code.type--smbk-color-code-type | light: #137575dark: #2ab5b5changes with theme | Color | |
| color.code.tag--smbk-color-code-tag | light: #3b5ce3dark: #7fa0ffchanges with theme | Color | |
| color.code.property--smbk-color-code-property | light: #213583dark: #b8ccffchanges with theme | Color | |
| color.code.comment--smbk-color-code-comment | light: #606a75dark: #9ba5b1changes with theme | Color | |
| color.code.punctuation--smbk-color-code-punctuation | light: #4e555edark: #c4cdd8changes with theme | Color | |
| motion.enter.duration--smbk-motion-enter-duration | 320ms | Motion | |
| motion.enter.easing--smbk-motion-enter-easing | cubic-bezier(0.16, 1, 0.3, 1) | Motion | |
| motion.feedback.duration--smbk-motion-feedback-duration | 150ms | Motion | |
| motion.feedback.easing--smbk-motion-feedback-easing | cubic-bezier(0.16, 1, 0.3, 1) | Motion | |
| blur.4--smbk-blur-4 | 4px | Blur | |
| size.target.comfortable--smbk-size-target-comfortable | 44px | Size |
Manifest
{
"name": "code-block",
"block": "smbk-code",
"summary": "Code in a well, colored by kind and numbered by line, with a top bar for a file name or language tabs and small icon actions such as copy.",
"adapt": "css",
"axes": {},
"flags": {
"bare": {
"description": "No top bar: just the well."
}
},
"parts": [
"bar",
"label",
"icon",
"tabs",
"actions",
"action",
"body",
"numbers",
"code",
"status",
"keyword",
"declaration",
"string",
"number",
"function",
"type",
"tag",
"property",
"comment",
"punctuation"
],
"figmaNodeId": "4255:6452"
}