Installation
pnpm dlx shadcn@latest add https://asciicn.dev/r/terminal-window.jsonUsage
import { TerminalWindow } from "@/components/asciicn/terminal-window";
import type { TerminalStep } from "@/components/asciicn/terminal-window";
const script: TerminalStep[] = [
{ kind: "command", text: "pnpm test" },
{ kind: "task", text: "Running 128 tests", duration: 1.5 },
{ kind: "output", text: "128 passed", tone: "ok" },
];
export function TestRun() {
return <TerminalWindow script={script} title="~/acme — test" rows={8} />;
}Leave script out to play the built-in session: npx shadcn add installs a component, retries once after a registry timeout, fills a progress bar, lists the files it wrote, builds the site and prints a frame of a torus in the standard ramp.
A script is a list of steps, each with a kind:
command: typed after the prompt one key at a time, with a small, repeatable jitter between keys, then entered.output: printed a line at a time.tonecolours it and sets the mark before it:ok(✔),info(›),warn(!),error(✖),mutedorplain. Setwrap: falseto clip long lines, for ASCII art, andaltto say what the art is to screen readers.task: aLoaderspinner fordurationseconds, then a result mark.resultisok,warnorerror, anddonereplaces the text when it ends.progress: aProgressBarin brackets that fills from 0 to 100% overdurationseconds, as in[████████░░] 82%. It stalls and spurts like a real install.wait: a pause with nothing printed.
After the last step a prompt waits with a blinking █ cursor. With loop, the session starts over 3.5 seconds later.
The window is rows lines tall and never changes size. New lines push older ones off the top, and long commands wrap.
Examples
Custom script
A deploy that fails a check, retries with the check skipped, uploads and goes live. Twelve rows, the light frame and size="md".
Landing hero
The default session at 1.25x speed with a headline. They sit side by side once the container is 760px wide, and stack below that.
Props
| Prop | Type | Default |
|---|---|---|
| script The session to play. Steps are command, output, task, progress and wait. | Type: readonly TerminalStep[] | Default DEFAULT_TERMINAL_SCRIPT |
| title Text in the title bar, such as a path or a command. Also names the window for screen readers. | Type: string | Default "~/acme — zsh" |
| speed Playback speed multiplier. | Type: number | Default 1 |
| loop Start over 3.5 seconds after the end. When false, the button in the bottom border becomes replay. | Type: boolean | Default true |
| rows Height of the screen in lines. | Type: number | Default 18 |
| prompt The prompt before each command. | Type: string | Default "$" |
| frame Box-drawing set for the window frame. | Type: "light" | "heavy" | "double" | "rounded" | "ascii" | Default "rounded" |
| color Frame colour, a palette name or any CSS colour. | Type: AsciiColor | Default "grey" |
| size Glyph cell height: 12, 14 or 16px. | Type: "sm" | "md" | "lg" | Default "lg" |
| cell Glyph cell height in CSS pixels. Overrides size. | Type: number | Default – |
TerminalWindow also takes the AsciiBox props shadow, filled, padding and labelAlign, and any div attribute.
Notes
- The screen is hidden from screen readers, because it changes many times a second. A visually hidden transcript of the whole session sits next to it, and the window is a group named by
title. - The clock runs only while the window is on screen and the tab is visible, so the session starts when the visitor scrolls to it.
- The bottom border holds a pause button, which turns into play when paused and into replay when a session without
loopends. It is a real button, reachable with Tab. - With reduced motion, the window shows the finished session as one still frame, with a steady cursor and no button.
- The text is DOM text in
--ascii-font, so it stays sharp at any zoom and can be selected and copied.