Terminal
A terminal on UI SDK's input history and stick-to-bottom: ANSI colors kept readable in every skin, links, streaming commands with progress lines, Tab completion, Ctrl+C and Ctrl+L, built-in help, clear and history, and agent actions.
A terminal for commands your app defines, like a web shell, a dev console or a CLI in the
browser. Built on UI SDK's createInputHistory (Up and Down) and createStickToBottom
(output follows its end unless you scroll up).
pnpm dlx shadcn@latest add superzero11/uiset/terminalUse
<Terminal
label="Shell"
prompt="~/app $"
welcome="Type help for the commands."
commands={{
echo: { description: "Prints its words", usage: "<words…>", run: (args) => args.join(" ") },
cat: {
description: "Prints a file",
usage: "<file>",
complete: () => Object.keys(files),
run: (args, io) => io.print(read(args[0])),
},
build: {
description: "Builds, with a progress bar",
async *run(_args, io) {
for (let p = 0; p <= 100; p += 10) {
await step(io.signal)
yield `\r${p}%`
}
yield "\n\x1b[32m✓\x1b[0m done\n"
},
},
}}
onCommand={(line, io) => sendToServer(line, { signal: io.signal, onOutput: io.print })}
/>
// Output from elsewhere, like a socket:
const [shell] = useState(createTerminalController)
<Terminal label="Shell" controller={shell} … />
socket.onmessage = (event) => shell.print(event.data)
- A command's
run(args, io)returns a string, a Promise of one, nothing (after callingio.printandio.error), or an async iterable that streams. A thrown error prints in the danger tone.io.signalaborts on Ctrl+C, and the prompt comes back at once even if the command doesn't listen. - Output goes through a small screen with a cursor, as a real terminal's does:
\rreturns to the line's start and what follows overwrites it (progress bars),\bsteps back, and the erase-line, column and cursor-up codes spinners use (\e[2K,\e[G,\e[A) redraw in place. An escape split across two chunks waits for the rest; styles carry across lines. - ANSI styles: the 16 colors as 9 tokens (bright black is a muted grey), backgrounds as tints, bold, dim, italic, underline, strikethrough, and inverse as a solid fill (so test runners' PASS and FAIL badges stay readable). 256-color and true-color codes keep the skin's colors. URLs and OSC 8 links become links, http(s) only. Control and bidi-override characters are dropped.
- The page catches up once a frame, however fast output comes, and only changed lines draw again. Old lines go only while you're following the end, so what you're reading stays put.
help,clearandhistorycome built in.onCommandgets lines no command matched.messagesreplaces the terminal's own words, for other languages.controller(createTerminalController()) writes output (print,error), runs and clears from outside, like a socket's messages.scrollbackcaps the lines kept (2000).- The colors are
--term-redand friends, checked at 4.5:1 on the terminal in every skin and scheme. "Black" and "white" follow the scheme instead of vanishing into it.
Keys
| Keys | Does |
|---|---|
| Enter | Runs the line |
| Up, Down | Earlier and later commands; Down past the newest brings back your draft |
| Tab | Completes a command, or an argument with complete (quoting words with spaces); a second Tab lists several. With nothing to complete, Tab moves on |
| Ctrl+C | Stops the running command, or drops the line; with text selected, copies |
| Ctrl+L | Clears the screen |
The output is a log region that takes focus for scrolling by keyboard; a click on it puts
the caret in the prompt unless you were selecting text.
For agents
run(command) and clear(). run takes one line of plain text: control characters,
which could fake what the person sees echoed, are refused. query({ within: "Shell" })
returns the last 50 lines as plain text, keyed by line. Commands can do anything your app
lets them, so gate run with the agent bridge's allow callback where that matters.
Screen readers
The output is a log that reads out new lines, not lines redrawn in place, so a progress bar is read once. Lines the terminal also announces (Ctrl+C, a completion list) are hidden from it. A very fast stream still reads every line; summarize long output in your command if that matters.