Navigation

v0.0.4
Typography

Code

Literal text in the mono face, inline or as a block.

On this page

Code is literal text in the mono face: a command, a value, a file name. Inline, .code on a <code> is a small chip that sits in the line. As a block, code-block on a <pre> is a well that scrolls on its own when a line is long.

Code

<p class="m(0) text-sm fg-text-body leading-md">Install the package with <code class="code">npm install @master/ui</code>, then import it after <code class="code">@master/css</code>.</p>

#Block

Put code-block on the <pre> and keep a <code> inside it for semantics. Lines keep their breaks, and the block scrolls sideways instead of wrapping. Give it tabindex="0" when its lines can be longer than the block, so the keys can scroll it.

Block

<pre class="code-block" tabindex="0"><code>@import '@master/css';@import '@master/ui';@theme {--color-brand: var(--color-indigo-50);}</code></pre>

#Lines

To say something about one line, make each line a code-line. The block then lays its lines out as a grid: every line is as wide as the longest one, so a band on a line runs the whole scrolling width, and the line breaks between the elements cost nothing, because white space between grid items is not drawn. Write one element per line of code.

code-line-highlight is the line being talked about: a tint, and a rule of ink at its start. code-numbered on the block numbers the lines with a CSS counter, in a gutter that is not selected or copied with the code.

Numbered lines and a highlight

<pre class="code-block code-numbered" tabindex="0"><code class="code-line">.btn {</code><code class="code-line">  --btn-height: 2.375rem;</code><code class="code-line code-line-highlight">  --btn-radius: var(--radius-control);</code><code class="code-line">  border-radius: calc(var(--btn-radius) * var(--corner-scale));</code><code class="code-line">  corner-shape: var(--corner-shape);</code><code class="code-line">}</code></pre>

#A diff

code-line-added and code-line-removed mark the lines of a change. The hue agrees with the sign the line already starts with: keep the + and the - in the text, as a patch has them, so the change reads without color and copies as a patch.

A diff

<pre class="code-block" tabindex="0"><code class="code-line">  @theme {</code><code class="code-line code-line-removed">-   --radius-control: .5rem;</code><code class="code-line code-line-added">+   --radius-control: .625rem;</code><code class="code-line">    --radius-surface: 1rem;</code><code class="code-line">  }</code></pre>

#Panel

code-panel puts a header over the block, in one well: the file's name in code-title and a control at the end, which sits 4px in with the concentric corner. The block inside gives up its own well.

Set --code-radius on a standalone code-block or on the code-panel that owns it. The default is var(--radius-control); a root held directly in a tray uses var(--radius-surface) so its corner follows the holder's inset. A panel's inner block stays square and transparent, keeping one contour around the composition.

Copying needs a script, and three lines are enough: read the block's text and hand it to the clipboard.

Panel

<div class="code-panel">  <div class="code-header">    <span class="code-title">app.css</span>    <button type="button" class="btn btn-quiet btn-sm" id="copy-app-css">      <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">        <path d="M9 9h10a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1V10a1 1 0 0 1 1-1M16 9V5a1 1 0 0 0-1-1H5a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h3" />      </svg>Copy</button>  </div><pre class="code-block" tabindex="0"><code>@import '@master/css';@import '@master/ui';@theme {--color-brand: var(--color-indigo-50);}</code></pre></div><script>  document.getElementById('copy-app-css').addEventListener('click', function (event) {  var button = event.currentTarget  navigator.clipboard.writeText(button.closest('.code-panel').querySelector('.code-block').textContent).then(function () {    button.lastChild.textContent = 'Copied'    setTimeout(function () { button.lastChild.textContent = 'Copy' }, 1600)  })  })</script>

#Code or kbd

Use code for what a reader types as text or reads from a program, and kbd for a key to press. The stylesheet does not highlight syntax; a highlighter's spans keep their own colors inside a block.

#Classes

ClassDescription
codeInline code, on a <code>: a small chip in the line
code-blockA block of code, on a <pre>: a well whose lines keep their breaks and scroll sideways. Give it tabindex="0" when a line can be longer than the block
code-lineOne line of a block, on a <code> or a <span>. With lines marked, the block lays them out as a grid and each is as wide as the longest
code-line-highlightThe line being talked about: a tint, and a 2px rule of ink at its start. With code-line
code-line-addedAn added line of a diff, in the success hue; keep the + in the text. With code-line
code-line-removedA removed line of a diff, in the danger hue; keep the - in the text. With code-line
code-numberedNumbers the code-line elements of the block with a CSS counter, in a gutter that is not selected or copied. With code-block
code-panelOne well around a header and a block; the block inside gives up its own well
code-headerA 40px strip over the block, with the name at the start and a control at the end. A copy button there needs a script that hands the block's text to the clipboard
code-titleThe name of the file or of the language, in the header

#Custom properties

PropertyDefaultDescription
--code-radiusvar(--radius-control)Circular radius of a standalone block before the corner scale. On code-block
--code-radiusvar(--radius-control)Circular radius of the panel before the corner scale, also used by its inset controls; use var(--radius-surface) when held directly in a tray. On code-panel

#Accessibility

  • Give a block tabindex="0" when a line can be longer than the block, so the keys can scroll it.
  • Line numbers are generated content: they are not read aloud, selected or copied.
  • A highlight is visual. When the text around the block refers to a line, name what is on it rather than its color or its number.

#Source

@master/ui/components/code.css
/* * Code: literal text in the mono face. Inline, it is a small chip in the line; as a block, a well that * scrolls on its own when a line is long. * A block may mark its lines. Each line is then a code-line, and the block lays them out as a grid, so a * line's band runs the whole scrolling width and the line breaks between the spans cost nothing. Lines can * be numbered by a CSS counter, highlighted, or marked as added and removed in a diff. * A panel puts a header over the block: the file's name and a control, in one well. */@layer components {  .code {    padding: .125em .375em;    border-radius: calc((var(--radius-chip) - 2px) * var(--corner-scale));    corner-shape: var(--corner-shape);    background: var(--code-tone, var(--color-surface-chip));    color: var(--code-ink, var(--color-text-strong));    box-shadow: inset 0 0 0 .5px var(--code-line, var(--color-line-contour));    font: 400 .875em/1.4 var(--font-family-mono);    font-variant-ligatures: none;    overflow-wrap: anywhere;    @media (forced-colors: active) {      outline: 1px solid CanvasText;      outline-offset: -1px;    }  }  /* On a <pre>. Lines keep their breaks and the block scrolls sideways. */  .code-block {    --code-radius: var(--radius-control);    --code-padding-x: 1rem;    --code-padding-y: .875rem;    margin: 0;    padding: var(--code-padding-y) var(--code-padding-x);    border-radius: calc(var(--code-radius) * var(--corner-scale));    corner-shape: var(--corner-shape);    overflow: auto;    background: var(--color-surface-inset);    color: var(--color-text-strong);    box-shadow: var(--shadow-pressed);    font: 400 .8125rem/1.65 var(--font-family-mono);    font-variant-ligatures: none;    tab-size: 2;    white-space: pre;    scrollbar-width: thin;    scrollbar-color: var(--color-line-strong) transparent;    overscroll-behavior-x: contain;    &:focus-visible {      @apply --focus-ring;    }    /*     * Marked lines: the block becomes a grid of lines, each as wide as the longest. White space between the     * line elements is not rendered in a grid, so the markup may keep one line of source per line of code.     */    &:has(.code-line) {      display: grid;      align-content: start;      counter-reset: code-line;    }    @media (forced-colors: active) {      outline: 1px solid CanvasText;      outline-offset: -1px;    }  }  /* One line of a block. It bleeds to the block's edges, so a band on it meets the well's contour. */  .code-line {    display: block;    min-height: 1lh;    margin-inline: calc(-1 * var(--code-padding-x));    padding-inline: var(--code-padding-x);    counter-increment: code-line;    /* The line being talked about: a tint, and a 2px rule of ink at its start. */    &.code-line-highlight {      background: color-mix(in oklab, var(--color-text-strong) 6%, transparent);      box-shadow: inset 2px 0 0 var(--color-text-strong);    }    /*     * A diff. The hue agrees with the sign the line already starts with, + or -, which is part of the text     * and is what a reader copies; a removed line also steps back in ink.     */    &.code-line-added {      background: color-mix(in oklab, var(--color-success) 11%, transparent);      box-shadow: inset 2px 0 0 var(--color-success);    }    &.code-line-removed {      background: color-mix(in oklab, var(--color-danger) 10%, transparent);      color: var(--color-text-body);      box-shadow: inset 2px 0 0 var(--color-danger);    }    @media (forced-colors: active) {      &:is(.code-line-highlight, .code-line-added, .code-line-removed) {        outline: 1px solid Highlight;        outline-offset: -1px;      }    }  }  /* Numbered: each line counts itself in a quiet gutter that is not selected with the code. */  .code-numbered {    .code-line::before {      content: counter(code-line);      display: inline-block;      width: 2ch;      margin-inline-end: 1.25rem;      color: var(--color-text-disabled);      text-align: end;      user-select: none;      -webkit-user-select: none;    }    .code-line:is(.code-line-highlight, .code-line-added, .code-line-removed)::before {      color: var(--color-text-muted);    }  }  /*   * Panel: one well around a header and a block. The block inside gives up its own well; the header is a   * 40px strip over a hairline, with the name at the start and a control 4px in from the end.   */  .code-panel {    --code-radius: var(--radius-control);    display: flex;    flex-direction: column;    min-width: 0;    margin: 0;    border-radius: calc(var(--code-radius) * var(--corner-scale));    corner-shape: var(--corner-shape);    overflow: clip;    background: var(--color-surface-inset);    box-shadow: var(--shadow-pressed);    .code-block {      border-radius: 0;      background: transparent;      box-shadow: none;      &:focus-visible {        outline-offset: -2px;      }    }    .btn {      --btn-height: 2rem;      --btn-radius: calc(var(--code-radius) - 4px / var(--corner-scale));      --btn-font-size: .78125rem;      --btn-padding-x: .625rem;      --icon-size: .875rem;      --icon-offset: -.0625rem;      min-height: var(--btn-height);    }    @media (forced-colors: active) {      outline: 1px solid CanvasText;      outline-offset: -1px;    }  }  .code-header {    display: flex;    flex: none;    align-items: center;    justify-content: space-between;    gap: .75rem;    min-height: 2.5rem;    padding-block: 0;    padding-inline: 1rem 4px;    border-bottom: 1px solid var(--color-line-hair);  }  /* The name of the file, or of the language. */  .code-title {    --icon-size: .875rem;    display: inline-flex;    align-items: center;    gap: .5rem;    min-width: 0;    margin: 0;    overflow: hidden;    color: var(--color-text-muted);    font: 400 .75rem/1.4 var(--font-family-mono);    text-overflow: ellipsis;    white-space: nowrap;  }}