Documentation

Getting Started

Everything you need to build terminal applications with Storm. From installation to production.

Installation

Storm requires Node.js 18+ and React 18+. Install the package from npm:

TERMINAL
$ npm install @orchetron/storm-tui

Or with your preferred package manager:

TERMINAL
$ yarn add @orchetron/storm-tui $ pnpm add @orchetron/storm-tui

Peer dependencies: React 18+ and react-reconciler are required. Storm ships its own custom reconciler -- you do not need react-dom.

Your First App

Create a file called app.tsx and paste the following. This renders a bordered box with a spinner and styled text, and exits cleanly on Ctrl+C.

APP.TSX
import React from "react"; import { render, Box, Text, Spinner, useInput, useTui } from "@orchetron/storm-tui"; function App() { const { exit } = useTui(); useInput((e) => { if (e.key === "c" && e.ctrl) exit(); }); return ( <Box borderStyle="round" borderColor="#82AAFF" padding={1}> <Spinner type="dots" color="#82AAFF" /> <Text bold color="#82AAFF"> Storm is running </Text> </Box> ); } render(<App />).waitUntilExit();

Run it with tsx or your preferred TypeScript runner:

TERMINAL
$ npx tsx app.tsx

How it works: render() enters the alternate screen buffer, sets up the cell-level diff renderer, and starts the React reconciler. waitUntilExit() returns a promise that resolves when exit() is called or the process receives SIGINT.

The Golden Rules

Storm's custom reconciler means some React patterns work differently. These three rules prevent the most common mistakes.

01

Use useRef + requestRender() for animation

React state updates go through the reconciler, which is too slow for scroll, animation, or live data. Mutate a ref and call requestRender() to trigger a direct paint + diff cycle.

Wrong
SLOW_SCROLL.TSX
// setState triggers reconciler on every scroll tick -- laggy const [offset, setOffset] = useState(0); useInput((e) => { if (e.key === "down") setOffset(o => o + 1); });
Correct
FAST_SCROLL.TSX
// Imperative mutation + requestRender() -- bypasses React entirely const offset = useRef(0); const { requestRender } = useTui(); useInput((e) => { if (e.key === "down") { offset.current++; requestRender(); } });
02

Use useCleanup() for teardown

React's useEffect cleanup runs asynchronously, which can miss teardown in a terminal environment. Storm's useCleanup() runs synchronously before the component unmounts, ensuring timers, streams, and listeners are properly removed.

Wrong
LEAKED_TIMER.TSX
// useEffect cleanup is async -- may not fire before process exits useEffect(() => { const id = setInterval(tick, 100); return () => clearInterval(id); }, []);
Correct
CLEAN_TIMER.TSX
// useCleanup runs synchronously -- guaranteed teardown const id = useRef<NodeJS.Timeout>(); id.current = setInterval(tick, 100); useCleanup(() => clearInterval(id.current));
03

ScrollView needs a height or flex constraint

ScrollView virtualizes its children. Without a bounded height, it has no viewport to virtualize against and will render all items at once, defeating its purpose.

Wrong
NO_CONSTRAINT.TSX
// No height constraint -- ScrollView renders everything <ScrollView> {items.map(i => <Text key={i}>{i}</Text>)} </ScrollView>
Correct
CONSTRAINED.TSX
// Explicit height -- virtualization works correctly <ScrollView height={20}> {items.map(i => <Text key={i}>{i}</Text>)} </ScrollView> // Or use flexGrow inside a flex container <Box flexDirection="column" height="100%"> <Text>Header</Text> <ScrollView flexGrow={1}> {items.map(i => <Text key={i}>{i}</Text>)} </ScrollView> </Box>

Components

Storm ships 92 components across 9 categories. Every component is designed for cell-level rendering -- no string concatenation, no ANSI escape hacks. See the full catalog at Components.

Category Count Examples
Layout 14 Box, Flex, Grid, Spacer, Divider
Content 12 Text, Markdown, SyntaxHighlight, Link
Input 11 TextInput, Select, Checkbox, DatePicker
Data Display 10 Table, List, Tree, KeyValue
Feedback 8 Spinner, Progress, Toast, Badge
Navigation 7 Tabs, Breadcrumb, CommandPalette
Overlay 6 Modal, Drawer, Tooltip, ContextMenu
Visualization 5 LineChart, BarChart, Sparkline, Heatmap
AI / Agent 19 OperationTree, MessageBubble, CodeDiff

Hooks

82 hooks organized into four tiers by frequency of use. Each tier builds on the previous -- start with Tier 0, reach for higher tiers as your app grows.

T0

Essential

Used in every app

useTui App context: exit(), requestRender(), dimensions
useInput Keyboard and mouse event handler
useColors Theme-aware color palette access
useTick Imperative tick loop for animation
useCleanup Synchronous teardown on unmount
T1

Common

Used in most apps

useFocus Focus management and tab ordering
useScroll Imperative scroll offset control
useTerminal Terminal size, capabilities, resize events
useAnimation Tween and spring animation primitives
T2

Interactive

Complex interactions

useBuffer Direct cell-buffer access for custom rendering
useCommandPalette Fuzzy-search command registration
useDragReorder Keyboard-based drag-and-drop reordering
T3

Headless

Logic-only behaviors -- bring your own UI

useSelectBehavior Single/multi selection logic
useTreeBehavior Expand/collapse tree navigation
useFormBehavior Field validation, dirty tracking, submission
useListBehavior Keyboard-navigable list with virtualization

Layout (Flexbox + Grid)

Storm implements a pure-TypeScript flexbox and CSS grid layout engine. Every Box is a flex container by default. Layout is computed in a single pass before painting to the cell buffer.

Full architecture deep dive →
LAYOUT.TSX
// Flexbox: horizontal split with gap <Box flexDirection="row" gap={1} height="100%"> <Box width={30} borderStyle="single"> <Text>Sidebar</Text> </Box> <Box flexGrow={1}> <Text>Main content</Text> </Box> </Box> // CSS Grid: 2x2 dashboard <Grid columns={2} rows={2} gap={1} height="100%"> <Box borderStyle="round"><Text>CPU</Text></Box> <Box borderStyle="round"><Text>Memory</Text></Box> <Box borderStyle="round"><Text>Network</Text></Box> <Box borderStyle="round"><Text>Disk</Text></Box> </Grid>

Supported layout props: flexDirection, flexGrow, flexShrink, flexBasis, flexWrap, alignItems, alignSelf, justifyContent, gap, padding, margin, width, height, minWidth, minHeight, maxWidth, maxHeight, overflow.

Theming

Storm includes 12 built-in themes and supports custom themes via createTheme(). Themes are applied through context and consumed by all components automatically.

THEME.TSX
import { createTheme, ThemeProvider } from "@orchetron/storm-tui"; const custom = createTheme({ name: "midnight", colors: { primary: "#82AAFF", secondary: "#C3E88D", background: "#0D1117", surface: "#161B22", text: "#E6EDF3", border: "#30363D", error: "#F85149", warning: "#D29922", success: "#3FB950", }, }); <ThemeProvider theme={custom}> <App /> </ThemeProvider>

Built-in Themes

storm (default)
forest
ember
amethyst
frost
solar
crimson
chalk
ocean
minimal
matrix
retro

Plugins

Plugins extend Storm with custom components, hooks, themes, and middleware. They support async setup, scoped effects, inter-plugin communication, and are sorted topologically by dependency.

PLUGIN.TS
import type { StormPlugin } from "@orchetron/storm-tui"; export const myPlugin: StormPlugin = { name: "my-plugin", version: "1.0.0", dependencies: [], async setup(ctx) { // Register components, hooks, or middleware ctx.registerComponent("MyWidget", MyWidget); ctx.registerHook("useMyData", useMyData); }, teardown() { // Cleanup on app exit }, };
USAGE.TSX
import { render } from "@orchetron/storm-tui"; import { myPlugin } from "./plugin"; render(<App />, { plugins: [myPlugin] }).waitUntilExit();

SSH Serving

Serve your Storm app over SSH. Each connection gets its own isolated React tree with full keyboard, mouse, and resize support. Built-in authentication, rate limiting, and session management.

SERVER.TS
import { StormSSHServer } from "@orchetron/storm-tui/ssh"; const server = new StormSSHServer({ hostKey: "./host.key", port: 2222, maxConnections: 50, auth: (ctx) => ctx.method === "publickey", }); server.onConnection((session) => { session.render(<App user={session.username} />); }); server.listen(); // $ ssh -p 2222 user@localhost

DevTools

Storm includes built-in time-travel DevTools. Enable them with a single line to get component inspection, render profiling, and frame-by-frame replay.

Full DevTools guide →
DEVTOOLS.TS
import { render, enableDevTools } from "@orchetron/storm-tui"; const app = render(<App />); enableDevTools(app);

Features

account_tree

Component Tree Inspector

Browse the rendered component hierarchy with props and state.

history

Time-Travel Replay

Record frames and scrub through render history.

speed

Render Profiler

Measure frame time, cell skip rate, and reconciler cost per component.

accessibility_new

Accessibility Audit

Check focus order, screen reader labels, and contrast ratios.

Testing

Storm provides renderForTest() -- a headless renderer that captures cell-buffer output without needing a real terminal. Works with any test runner (Vitest, Jest, Node test runner).

APP.TEST.TSX
import { describe, it, expect } from "vitest"; import { renderForTest } from "@orchetron/storm-tui/test"; import { App } from "./app"; describe("App", () => { it("renders the welcome message", () => { const { lastFrame, unmount } = renderForTest(<App />); expect(lastFrame()).toContain("Storm is running"); unmount(); }); it("exits on Ctrl+C", async () => { const { stdin, waitUntilExit } = renderForTest(<App />); stdin.send({ key: "c", ctrl: true }); await waitUntilExit(); }); });

Test utilities: lastFrame() returns the rendered text output. stdin.send() simulates keyboard input. frames() returns an array of all rendered frames for snapshot testing.