@m4ike1/ion-tui
v0.1.1
Published
Terminal User Interface library with differential rendering for efficient text-based applications
Maintainers
Readme
@m4ike1/ion-tui
Terminal UI library with differential rendering for interactive CLI applications. Renders a tree of components to the terminal, updating only changed lines for flicker-free output.
Install
Part of the ion monorepo. From the repo root:
npm installImport from the package entrypoint:
import { TuiMainScreen, Text, type TUI } from "@m4ike1/ion-tui";Requires Node >= 22.19.
Quick start
import { type TUI, Text, Editor, ProcessTerminal, TuiMainScreen, matchesKey } from "@m4ike1/ion-tui";
const terminal = new ProcessTerminal();
const tui: TUI = new TuiMainScreen(terminal);
tui.addChild(new Text("Welcome to my app!"));
const editor = new Editor(tui, { borderColor: (s) => s, selectList: { /* ... */ } });
editor.onSubmit = (text) => {
tui.addChild(new Text(`You said: ${text}`));
};
tui.addChild(editor);
tui.setFocus(editor);
// In raw mode Ctrl+C does not send SIGINT; handle exit explicitly.
tui.addInputListener((data) => {
if (matchesKey(data, "ctrl+c")) {
tui.stop();
process.exit(0);
}
return undefined;
});
tui.start();See test/chat-simple.ts for a complete example (run with npx tsx test/chat-simple.ts).
Choosing a renderer
TUI is the shared interface for component management, focus, overlays, input, lifecycle, and rendering. Pick a concrete renderer at construction:
TuiMainScreen(terminal, showHardwareCursor?, logDirectory?)renders into the main buffer and preserves terminal scrollback.TuiAltScreen(terminal, showHardwareCursor?, logDirectory?, options?: TuiAltScreenOptions)renders a fixed-height viewport in the alternate buffer with application-owned scrolling. Onstop()it restores the main buffer and prints the final document.
Use isViewportTUI(tui) to narrow to ViewportTUI before calling setLayoutRoot(). VStack, HStack, and ScrollView layout semantics apply to the alternate-screen viewport; on TuiMainScreen the terminal owns scrollback. See docs/explanation.md.
Components
| Component | Purpose |
|---|---|
| Container | Groups children (addChild, removeChild, clear) |
| Box | Container with padding and background function |
| Text / TruncatedText | Wrapped multi-line text / single truncated line |
| Input | Single-line input with history, undo, kill ring |
| Editor | Multi-line editor with autocomplete and paste handling |
| Markdown | Markdown rendering with theme and LaTeX option |
| Loader / CancellableLoader | Animated spinner / abortable spinner (signal, onAbort) |
| SelectList | Keyboard-navigable option list with filtering |
| SettingsList | Settings rows with value cycling and submenus |
| ScrollView | Scrollable region for alt-screen layouts |
| VStack / HStack | Constrained flex layout for alt-screen layouts |
| VirtualList | Windowed per-message cache for large transcripts |
| Image | Inline image (Kitty / iTerm2 protocols, text fallback) |
| MouseRegion | Adds mouse handling without changing rendering |
| Spacer | Vertical spacing |
Full constructor and option details: docs/reference.md.
Input
Detect keys with matchesKey() and the Key helper (Kitty keyboard protocol aware):
import { matchesKey, Key } from "@m4ike1/ion-tui";
if (matchesKey(data, Key.ctrl("c"))) process.exit(0);
else if (matchesKey(data, Key.enter)) submit();
else if (matchesKey(data, Key.escape)) cancel();String identifiers ("enter", "ctrl+c", "shift+tab") also work. Default bindings for editor and alt-screen actions live in TUI_KEYBINDINGS and are customizable via KeybindingsManager and setKeybindings(). TuiAltScreen normalizes SGR mouse input into TuiMouseEvent; TuiMainScreen does not capture mouse input.
Custom components
Every line returned by render(width) must fit within width; the TUI errors otherwise. Use truncateToWidth(), wrapTextWithAnsi(), and visibleWidth() from the package. Cache rendered output and clear it in invalidate(). Components showing a text cursor implement Focusable and emit CURSOR_MARKER at the cursor position; containers with an embedded Input/Editor must propagate focused to the child for correct IME candidate-window placement. Full contract: docs/reference.md; recipes: docs/how-to.md.
Testing
There is no VirtualTerminal export in the public API. Tests in this package use the in-repo helper test/virtual-terminal.ts (backed by @xterm/headless) with TuiMainScreen / TuiAltScreen. Package tests run with node --test (see package.json); run the suite via ./test.sh from the repo root.
Debug
ION_TUI_WRITE_LOG=/tmp/tui-ansi.logcaptures the raw ANSI stream written to stdout.ION_TUI_ESC_TIMEOUToverrides the lone-Escape reassembly timeout in milliseconds (higher on SSH transports by default).
Further docs
docs/reference.md— complete public API reference.docs/how-to.md— task recipes (layouts, overlays, custom components, keybindings, testing).docs/explanation.md— rendering model and architecture concepts.
