# Log

What a program prints as it runs: wrapped lines in a well that keeps the newest in view until the reader scrolls up.

Block: `smbk-log`. Status: experimental. Figma: the Figma file, https://www.figma.com/community/file/1688324308358870630. Desktop and mobile: differ by presentation only.

## Options

| Axis | Values | Default | Meaning |
|---|---|---|---|
| `state` | `running`, `finished` | `running` | Running while the program prints, finished once it stops. The badge says which, and a screen reader hears when it starts and when it ends. |

Parts: `smbk-log__header`, `smbk-log__title`, `smbk-log__status`, `smbk-log__well`, `smbk-log__scroll`, `smbk-log__note`, `smbk-log__divider`, `smbk-log__lines`, `smbk-log__jump`, `smbk-log__announcement`

## Markup

```html
<div class="smbk-log smbk-log--state-running" data-following="true">
  <div class="smbk-log__header">
    <span class="smbk-log__title" id="log-1-title">Preview output</span>
    <span class="smbk-badge smbk-badge--look-soft smbk-badge--tone-info smbk-badge--size-sm smbk-log__status">
      <span class="smbk-badge__label">Running</span>
    </span>
  </div>
  <div class="smbk-log__well">
    <div
      class="smbk-log__scroll"
      data-scrollbar=""
      role="region"
      tabindex="0"
      aria-labelledby="log-1-title"
    >
      <pre class="smbk-log__lines">› npm run build
  vite v8.0.2 building for production…
  ✓ 412 modules transformed.
  dist/index.html                  0.62 kB
  dist/assets/index-4f1c2a.css    38.40 kB
  dist/assets/index-9b7e11.js    184.12 kB</pre>
    </div>
  </div>
  <span role="status" class="smbk-log__announcement">Preview output: Running</span>
</div>
```

## React

```tsx
import { Log } from './design-system/components/log/log.js';

<Log state="running" title="Preview output" lines={["› npm run build", "  vite v8.0.2 building for production…", "  ✓ 412 modules transformed.", "  dist/index.html                  0.62 kB", "  dist/assets/index-4f1c2a.css    38.40 kB", "  dist/assets/index-9b7e11.js    184.12 kB"]} />
```

## Stylesheet

```css
/* smbk-log — Log. Figma "Log" 7118:1670 (State: Empty, Running, Finished, Scrolled up; Header boolean).
 * What a program prints as it runs: a header with the log's title in Label MD and a small soft badge that
 * says Waiting, Running or Finished; then the well, the sunken surface inside a subtle hairline with the
 * small container corners, holding an optional note ("40 earlier lines left out") over a divider and the
 * lines in Code MD, which wrap so nothing scrolls sideways. The well is no taller than a long list and
 * scrolls inside itself; while the reader is scrolled up, "Jump to latest" waits at its foot. */

.smbk-log {
  box-sizing: border-box;
  /* Holds the status line, which is out of sight, inside the log rather than wherever the page would put it. */
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--smbk-space-stack-sm);
  width: 100%;
  color: var(--smbk-color-text-default);
}

.smbk-log__header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--smbk-space-inline-sm);
}

.smbk-log__title {
  color: var(--smbk-color-text-default);
  font-family: var(--smbk-typography-label-md-font-family);
  font-size: var(--smbk-typography-label-md-font-size);
  font-weight: var(--smbk-typography-label-md-font-weight);
  line-height: var(--smbk-typography-label-md-line-height);
  letter-spacing: var(--smbk-typography-label-md-letter-spacing);
  text-transform: var(--smbk-typography-label-md-text-transform);
}

.smbk-log__well {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  max-height: var(--smbk-size-list-max-height);
  overflow: hidden;
  border: var(--smbk-border-width-hairline) solid var(--smbk-color-border-subtle);
  border-radius: var(--smbk-radius-container-sm);
  background: var(--smbk-color-surface-sunken);
}

/* The well's ring when its scrolling area has the keyboard, drawn inside its edge. */
.smbk-log__well:has(.smbk-log__scroll:focus-visible) {
  outline: var(--smbk-focus-ring-width) solid var(--smbk-color-border-focus);
  outline-offset: calc(-1 * var(--smbk-focus-ring-width));
}

.smbk-log__scroll {
  box-sizing: border-box;
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: var(--smbk-space-stack-sm);
  min-height: 0;
  padding: var(--smbk-space-inset-lg);
  overflow-x: hidden;
  overflow-y: auto;
  outline: none;
  /* 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;
}

/* Figures of one width, so a count that grows as the program prints ("40 earlier lines left out") never moves the
   words after it. */
.smbk-log__note {
  margin: 0;
  color: var(--smbk-color-text-subtle);
  font-family: var(--smbk-typography-body-sm-font-family);
  font-size: var(--smbk-typography-body-sm-font-size);
  font-weight: var(--smbk-typography-body-sm-font-weight);
  line-height: var(--smbk-typography-body-sm-line-height);
  letter-spacing: var(--smbk-typography-body-sm-letter-spacing);
  font-variant-numeric: tabular-nums;
}

.smbk-log__divider {
  flex-shrink: 0;
}

/* The lines: the code style, always left to right, wrapping at any point so a long line never runs past
   the well. */
.smbk-log__lines {
  margin: 0;
  color: var(--smbk-color-text-default);
  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-wrap;
  overflow-wrap: anywhere;
  direction: ltr;
  text-align: left;
}

/* Nothing printed yet: the waiting words in the subtle text color. */
.smbk-log__lines--empty {
  color: var(--smbk-color-text-subtle);
}

.smbk-log__jump {
  display: flex;
  flex-shrink: 0;
  justify-content: flex-end;
  padding: 0 var(--smbk-space-inset-lg) var(--smbk-space-inset-lg);
}

/* What changed, for a screen reader: in the page, not drawn. */
/* .smbk-log__announcement: hidden from view, kept for screen readers and the keyboard, by the shared rule in visually-hidden/visually-hidden.css. */
.smbk-log__announcement {
  clip-path: inset(50%);
}

/* ---- high contrast (forced colors) ---- */

@media (forced-colors: active) {
  .smbk-log__well:has(.smbk-log__scroll:focus-visible) {
    outline-color: Highlight;
  }
}
```

## Tokens it reads

```json
{
  "space": {
    "stack": {
      "sm": {
        "$type": "dimension",
        "$value": "{{space.8}}",
        "$description": "Gap between things stacked one above another, small."
      }
    },
    "inline": {
      "sm": {
        "$type": "dimension",
        "$value": "{{space.6}}",
        "$description": "Gap between things side by side, small."
      }
    },
    "inset": {
      "lg": {
        "$type": "dimension",
        "$value": "{{space.12}}",
        "$description": "Padding inside a component, large."
      }
    }
  },
  "color": {
    "text": {
      "default": {
        "$type": "color",
        "$value": "{{color.grey.900}}",
        "$description": "Main text."
      },
      "subtle": {
        "$type": "color",
        "$value": "{{color.grey.700}}",
        "$description": "Secondary text: descriptions, metadata."
      }
    },
    "border": {
      "subtle": {
        "$type": "color",
        "$value": "{{color.grey.300}}",
        "$description": "Dividers and quiet edges."
      },
      "focus": {
        "$type": "color",
        "$value": "{{color.red.600}}",
        "$description": "Keyboard focus ring on controls."
      }
    },
    "surface": {
      "sunken": {
        "$type": "color",
        "$value": "{{color.alpha.grey-800.5}}",
        "$description": "Recessed wells: fields, tracks, code. See-through, so a well always shows one step below what it sits on: the page, a card or a panel."
      }
    }
  },
  "typography": {
    "label-md": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist, Geist Fallback, system-ui, sans-serif",
        "fontSize": "14px",
        "fontWeight": 500,
        "lineHeight": 1.4,
        "letterSpacing": "0px"
      },
      "$description": "Label MD: labels on controls and short UI text (buttons, chips, tabs, menu headings). Geist Medium, sentence case. Code and keys use Code SM and Code MD."
    },
    "body-sm": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist, Geist Fallback, system-ui, sans-serif",
        "fontSize": "14px",
        "fontWeight": 400,
        "lineHeight": 1.5,
        "letterSpacing": "0px"
      }
    },
    "code-md": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist Mono, Geist Mono Fallback, ui-monospace, monospace",
        "fontSize": "14px",
        "fontWeight": 400,
        "lineHeight": 1.6,
        "letterSpacing": "0px"
      },
      "$description": "Code MD: the code block and larger keys. Geist Mono with its own line height, 160%, so lines of code read apart."
    }
  },
  "size": {
    "list": {
      "max-height": {
        "$type": "dimension",
        "$value": "{{size.width.320}}",
        "$description": "The greatest height of a scrolling list or menu (the select, the dropdown, the combobox): 320 (decision H). The list scrolls inside it."
      }
    }
  },
  "border-width": {
    "hairline": {
      "$type": "dimension",
      "$value": "{{border-width.1}}",
      "$description": "Thin lines: dividers and almost every edge."
    }
  },
  "radius": {
    "container": {
      "sm": {
        "$type": "dimension",
        "$value": "{{radius.8}}",
        "$description": "Corners of cards, panels and dialogs, small."
      }
    }
  },
  "focus": {
    "ring": {
      "width": {
        "$type": "dimension",
        "$value": "{{border-width.2}}",
        "$description": "Thickness of the focus ring."
      }
    }
  }
}
```

## Purpose

Shows what a program prints as it runs, such as a build, an upload or a job, and keeps it after the program finishes. The newest line stays in view while it prints, unless the reader scrolls up to read.

## Anatomy

- The block: the header, then the well.
- The header part: the title part in a medium label style, then the status part, a small soft badge that says Waiting, Running or Finished.
- The well part, the sunken surface inside a subtle hairline with the small container corners, no taller than a long list.
- The scroll part, the area inside the well that scrolls.
- The note part, an optional line in a small body style and the subtle text color, such as "40 earlier lines left out", with the divider part under it.
- The lines part, the output in the code style, left to right, wrapping at the well's width. Before anything is printed it says "Waiting for output…" in the subtle text color.
- The jump part, a small ghost "Jump to latest" button at the well's foot, there only while the reader is scrolled up.
- The announcement part, a status line a screen reader hears, in the page but not drawn.

## When to use

- For the output of a build, an upload, a test run or a script, while it runs and after.
- For any text that arrives line by line and is read as it comes.

## When not to use

- For code a reader copies or studies. Use a [code block](code-block.md).
- For messages a reader must act on. Use an [alert](alert.md) or a [toast](toast.md).
- For a list of past events with times and people. Use a [timeline](timeline.md).

## Behavior

Set `state` to running while the program prints and to finished once it stops; the badge follows, in the info color while running and the success color when finished. With no lines yet it says Waiting.

While running, the well keeps the newest line in view as lines arrive and as the lines rewrap. When the reader scrolls up, the log stops following, so the lines they are reading stay still, and "Jump to latest" appears at the well's foot. Pressing it goes to the newest line at once, follows again and puts the keyboard back in the well; scrolling back to the end does the same. A page can also hold the following itself with `following` and `onFollowingChange`.

Lines wrap at any point, so nothing scrolls sideways, on a phone too. Keep the lines you pass to a few hundred; for more, drop the oldest and say so in the note.

## Accessibility

### What it does for you

- The well is one stop for the Tab key and a region named by the title (or by `aria-label` when the log has no title), so the arrow keys scroll it. It shows the focus ring inside its edge, drawn in the system's highlight color in a forced-color mode.
- The lines are never read out one by one: hundreds of lines would drown everything else. A screen reader hears when the log starts printing ("Build output: Running") and when it finishes ("Build output: Finished").
- `status` adds the page's own short status, such as "120 lines printed", heard when it changes.
- Every word it says by itself, such as the state on its badge and the jump button, keeps its English unless the page gives its own.

### What you must do

- Name the log for what printed it, with a title or an `aria-label`. Without a name the wrapper warns in development.
- Change `status` seldom: every few seconds at most.
- Don't say an error by the color of a line alone; say it in words in the output, or in an alert beside the log.
- Give the state's words and the jump button's label in the page's own language when the page is not in English.

### How to test

- With the keyboard, Tab reaches the log's well once, with the focus ring inside its edge. The arrow keys scroll it, and Home and End jump to the first and last lines.
- Scroll up while it runs: the log stops following, and "Jump to latest" appears at the well's foot. Press it with Enter or Space: the newest line shows and the keyboard is back in the well.
- With a screen reader, the log is read as a region by its title, and it announces "Running" and "Finished" but not each line.
- With text at 200% and the page 320 pixels wide, the lines wrap and nothing scrolls sideways.
- In a forced-color mode, the focus ring around the well is visible.

### Keyboard

| Key | What it does |
|---|---|
| Tab | Moves focus to the well, which is one stop. |
| Down Arrow or Up Arrow | Scrolls the lines. |
| Home or End | Scrolls to the first or last line. |
| Enter or Space | On "Jump to latest", goes to the newest line, follows again and puts the keyboard back in the well. |

## Usage rules

### Do

- Name the log for what printed it.
- Say in the note what was left out when you drop old lines.
- Set `state` to finished when the program stops.

### Don't

- Announce each line.
- Color lines to mean errors with no words.
- Use a log for code to copy.
