Terminal
A dark mono command block with a phosphor prompt glyph and a fixed trailing copy action.
- Status
- Since
0.1.0- Accessibility pattern
- decorative prompt + real text
Last updated
Install
Add Terminal from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.
pnpm dlx shadcn@latest add @vegastack/terminalThe same command installs the registry items it composes: @vegastack/copy-button, @vegastack/use-overflow.
Usage
import { Terminal } from "@/components/ui/terminal";
<Terminal
title="Install"
lines={[
"pnpm dlx shadcn@latest add @vegastack/button",
{ output: "✓ Installed 1 component" },
]}
/>;Terminal self-scopes to the marketing dark ground (.vs-marketing) — it reads correctly even
embedded in a light-theme docs page (like this one), with no
MarketingSurface wrapper required.
Anatomy
Terminal is a compound component. Every exported part, with the
data-slot it renders (generated from the canonical source):
Examples
Multi-line, custom prompt
Each line is a plain string (a command) or { output: string } (dimmed, no prompt glyph). The
prompt glyph — the sanctioned "terminal prompt glyph" --brand marker role — defaults to $ and
is overridable.
Copying
The command row composes the icon-only CopyButton at its inline
end. The command pane scrolls independently, so the action remains visible even when a command is
long. It copies every command line (not output lines), joined by newline, unless you pass an
explicit copyValue; successful writes swap the copy icon for the standard success check.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
lines* | (string | TerminalLine)[] | — | The lines to render, top to bottom. A plain string is shorthand for
{ command: string }. Content is fully caller-supplied (no timestamps,
no randomness) — deterministic by construction, so it's VRT-stable. |
aria-label | string | derived from `title` | Accessible name for the scrollable command pane, which is a keyboard focus
stop while it overflows. Overrides the default, which names the pane from the
visible title. Mirrors ScrollArea: the label is intercepted here and
applied to the focusable element, not to the outer block. |
aria-labelledby | string | the visible title element | Id of an element naming the scrollable command pane. Takes precedence over
the visible title; ignored when aria-label is set. |
copyValue | string | derived from command lines | Value copied by the trailing CopyButton. Defaults to every command
line (not output lines) joined with newlines. |
prompt | string | '$' | The prompt glyph shown before each command line — the sanctioned
"terminal prompt glyph" --brand marker role (audit 17-brand-direction
§Color & surface). |
title | React.ReactNode | 'Terminal' | Small mono uppercase label in the header bar. |
Data attributes and CSS variables on Terminal
| Attribute | Values |
|---|---|
data-slot | "terminal" | "terminal-command-row" | "terminal-copy" | "terminal-header" | "terminal-line" | "terminal-prompt" |
Accessibility
- The prompt glyph is decorative (
aria-hidden) — command text itself is real, readable text. - The composed
CopyButtoncarries its own accessible name ("Copy command"/"Copied command") and announces the copy via a visually-hidden live region. - The command pane is keyboard-focusable while it actually scrolls, and not otherwise. A
scrollable region that cannot be focused is unreachable without a pointer; a pane whose commands
fit needs no tab stop, and adding one would put a dead stop in front of every keyboard user on a
page full of one-line install snippets. The pane measures itself live (
useOverflow), so the tab stop appears and disappears with the viewport width. Focus is shown as an inset outline: an outward one is not painted at all here, because the block's root isoverflow-hiddenandscroll-fade-xmasks the pane to its own border box. A border tint is not an option either —forced-colors: activereplacesborder-color, which would leave the pane with no indicator in the forced palette. - The pane is always announced as a named group, labelled by the visible
title— the name and the role do not move with the measurement, because a role that appeared and vanished under a screen reader as the window resized would be a worse defect than the tab stop it removed — sotitle="Install"reads as "Install, group". A bare focusabledivwould announce as an unnamed stop: it maps to thegenericrole, which prohibits naming, so anaria-labelon it is not reliably exposed.groupis deliberate rather thanregion, which is a landmark — several install snippets on one page should not add several landmarks. Override the name witharia-labelor pointaria-labelledbyat your own element; both are applied to the pane, not the outer block. - Content is fully caller-supplied — no timestamps, no randomness — so the block is deterministic and safe for visual capture.
| Key | Action |
|---|---|
| Tab | Move focus to the command pane when it scrolls (announced by its name), then on to the copy button. |
| ← / → | Scroll a clipped command horizontally while the pane has focus. |
| Enter / Space | Copy the command, when the copy button has focus. |
| Contract | States tested |
|---|---|
| Behaviour | default, copied |
| Accessibility | browser-accessibility-test, focus-visible, keyboard, labeled, native-or-base-ui-semantics |
| Visual | default |