Getting Started with Terminal
@sigx/terminal lets you build terminal user interfaces (TUIs) and interactive CLI apps using the same reactive model and TSX components you use on the web. It is built on @sigx/reactivity and @sigx/runtime-core, and ships a cell renderer that paints your component tree to the terminal as ANSI-styled lines.
The package is an umbrella: it re-exports the terminal renderer, the headless @sigx/terminal-zero foundation and the themed @sigx/terminal-ui components, so you can import everything you need from a single @sigx/terminal entry. See Architecture for how the pieces fit together.
What you get
- TSX intrinsics for the terminal —
box,text, andbr— with colors, borders, labels and drop shadows. - A render entry (
renderTerminal/terminalMount) withinlineandfullscreenrender modes, a non-TTY plain-text fallback and a reactivegetTerminalSize(). - Layered keyboard input —
onKey(handler, { layer })dispatch with built-in Tab/Shift+Tab focus traversal and afocusStatesignal. - A themed component library — forms (
Input,Select,MultiSelect, …), feedback (Spinner,ProgressBar,Badge), navigation, layout, data, fx and task components. See Components. - A prompt kit — imperative
text/select/confirm/spinnerwizards. See Prompts.
Install
pnpm add @sigx/terminalThe umbrella brings the reactive engine (@sigx/reactivity, @sigx/runtime-core) and the whole TUI stack with it — there is nothing else to add. See Installation for the required TSX/Vite wiring.
A minimal app
Every .tsx file that uses terminal intrinsics needs the JSX pragma at the top so TSX compiles against the terminal runtime:
/** @jsxImportSource @sigx/terminal */
import { component, defineApp, signal } from '@sigx/terminal';
const App = component(() => {
const count = signal(0);
const tick = () => count.value++;
return () => (
<box border="rounded" label="Counter" color="cyan">
<text>Count is {count.value}</text>
<br />
<text color="green">Press the button to increment.</text>
</box>
);
});
defineApp(<App />).mount({ clearConsole: true });
defineApp(<App />).mount(...) works without passing a mount function because the terminal runtime registers terminalMount as the platform default. Any reactive change in your tree schedules a repaint of the whole screen.
Adding interactivity
The runtime ships focusable components with two-way model binding. Tab and Shift+Tab move focus between them:
/** @jsxImportSource @sigx/terminal */
import { component, defineApp, signal } from '@sigx/terminal';
import { Input, Button } from '@sigx/terminal';
const App = component(() => {
const name = signal('');
return () => (
<box border="single" label="Greeting">
<Input model={() => name} placeholder="Your name" autofocus label="Name" />
<br />
<text>Hello, {name.value || 'stranger'}!</text>
<br />
<Button label="Done" onClick={() => console.log(name.value)} />
</box>
);
});
defineApp(<App />).mount({ clearConsole: true });
Loading data
Terminal apps are full SignalX clients, so the value-first async primitives work here too. @sigx/terminal re-exports useData (along with useStream, useAction and all): call it during setup with a key and a fetcher, then render every state through .match(). No window is required — the terminal runtime declares itself a live client, so keyed reads actually fetch instead of parking at pending.
/** @jsxImportSource @sigx/terminal */
import { component, defineApp, useData } from '@sigx/terminal';
const App = component(() => {
const joke = useData('joke', async (key, { signal }) => {
const res = await fetch('https://icanhazdadjoke.com/', {
headers: { Accept: 'application/json' },
signal,
});
return (await res.json()).joke as string;
});
return () => (
<box border="rounded" label="Dad joke" color="cyan">
{joke.match({
pending: () => <text color="dim">Loading…</text>,
error: (e) => <text color="warn">Failed: {e.message}</text>,
ready: (text) => <text>{text}</text>,
})}
</box>
);
});
defineApp(<App />).mount({ clearConsole: true });
The fetcher receives the resolved key and an AbortSignal — pass it straight to fetch. useStream follows the same shape for progressive, token-at-a-time output, which is handy for piping an LLM response into a <text> cell.
Next steps
- Architecture — the renderer, headless foundation, UI library and args parser.
- Installation — install command, peer deps and TSX/Vite config.
- Layout & Styling — boxes, borders, colors and the line layout model.
- Render modes — inline vs fullscreen, non-TTY and resize.
- Input & Interactive Components — focus, keyboard input and the built-in widgets.
- Components — the full themed component library.
- Prompts — imperative CLI wizards.
- API Reference — every export, grouped by kind.
