Terminal
A mono command block on the card surface 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
Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Terminal before installation. Retain the digest printed by this command.
pnpm exec vegastack-design verify terminalInstall with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.
pnpm dlx shadcn@4.21.0 add @vegastack/terminalRun the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.
The 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" },
]}
/>;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 label in the header bar, in sentence case. |
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 |