TermUI Reference
TermUI is a TypeScript terminal UI framework built on React/Ink. 101 components + AI components, 8 themes, 12 hooks. Shadcn-style: npx termui add <component> copies source files into your project.
Always wrap the app root in <ThemeProvider>. Components read colors from the theme via useTheme().
CLI
npx termui create my-app # scaffold a new project (4 templates: minimal, cli, dashboard, wizard)
npx termui init # setup termui in an existing project
npx termui add spinner table select # add components (copies source into project)
npx termui list # browse all components
npx termui theme dracula # switch project theme
npx termui preview <component> # live preview
Component Categories
See references/components.md for full props on every component.
| Category | Components |
|---|---|
| Layout | Box Stack Grid ScrollView Columns Center Divider Spacer AspectRatio |
| Typography | Text Heading Code Link Badge Tag Markdown StreamingText JSONView Gradient BigText Digits |
| Input | TextInput TextArea PasswordInput NumberInput SearchInput MaskedInput EmailInput PathInput |
| Selection | Select MultiSelect RadioGroup Checkbox CheckboxGroup Toggle TreeSelect TagInput ColorPicker |
| Data | Table DataGrid List VirtualList Tree DirectoryTree KeyValue Definition Card Panel DiffView |
| Feedback | Spinner ProgressBar ProgressCircle StatusMessage Toast Alert Banner Skeleton |
| Navigation | Tabs TabbedContent Breadcrumb Pagination CommandPalette Menu Sidebar |
| Overlays | Modal Dialog Drawer Tooltip Popover |
| Forms | Form FormField Wizard Confirm DatePicker TimePicker FilePicker |
| Charts | Sparkline BarChart LineChart PieChart HeatMap Gauge |
| Utility | Timer Stopwatch Clock Clipboard KeyboardShortcuts Help ErrorBoundary Log Image QRCode |
| Templates | SplashScreen AppShell WelcomeScreen LoginFlow SetupFlow UsageMonitor InfoBox BulletList HelpScreen |
| AI | ChatThread ChatMessage ToolCall ThinkingBlock ToolApproval TokenUsage ContextMeter ModelSelector FileChange |
Hooks
All imported from termui/core:
// Keyboard input
useInput((input, key) => {
if (key.upArrow) {
/* ... */
}
if (key.escape) {
/* ... */
}
if (key.ctrl && input === 'c') process.exit(0);
});
// Focus management
const { isFocused } = useFocus({ autoFocus: true, id: 'my-input' });
const { focus, focusNext, focusPrev } = useFocusManager();
// Theme
const theme = useTheme(); // read current theme
const setTheme = useThemeUpdater(); // switch theme at runtime
// Terminal
const { columns, rows, hasColor } = useTerminal();
const { columns, rows } = useResize(); // reactive to resize
// Animation & timing
const frame = useAnimation(12); // 12fps frame counter
useInterval(() => tick(), 1000); // runs every 1000ms
// Async state
const { data, loading, error } = useAsync(() => fetchData(), []);
// Declarative keybindings
useKeymap([
{ key: 'q', description: 'Quit', handler: () => process.exit(0) },
{ key: 'r', description: 'Refresh', handler: refresh },
]);
// Clipboard
const { copy, paste } = useClipboard();
Theming
import {
ThemeProvider, AutoThemeProvider, createTheme, useTheme, useThemeUpdater,
defaultTheme, draculaTheme, nordTheme, catppuccinTheme,
monokaiTheme, solarizedTheme, tokyoNightTheme, oneDarkTheme,
} from 'termui/core';
// Wrap app
<ThemeProvider theme={draculaTheme}><App /></ThemeProvider>
// Auto dark/light
<AutoThemeProvider darkTheme={draculaTheme} lightTheme={nordTheme}><App /></AutoThemeProvider>
// Custom theme (merges with default)
const myTheme = createTheme({
name: 'brand',
colors: { primary: '#E11D48', accent: '#F97316', focusRing: '#E11D48' },
});
Token shape (all colors.* are hex strings):
primary primaryForeground secondary secondaryForeground accent accentForeground
success warning error info (each has a *Foreground variant)
background foreground muted mutedForeground border
focusRing selection selectionForeground
In a component: always fall back to theme tokens, never hardcode hex:
const theme = useTheme();
const color = props.color ?? theme.colors.primary;
Library Adapters
import { intro, text, select, spinner, tasks } from 'termui/clack'; // @clack/prompts wrapper
import pc from 'termui/picocolors'; // picocolors + pc.hex(), pc.theme.primary()
import { matter, matterWithYaml } from 'termui/gray-matter';
import { useGit, GitStatus } from 'termui/git';
import { useKeychain } from 'termui/keychain';
import { EmbeddedTerminal } from 'termui/pty';
import { createCLI, createOutput, withGracefulExit } from 'termui/args';
import { useGitHub } from 'termui/github';
import { useChat, useCompletion } from 'termui/ai'; // AI streaming hooks
import { createConversationStore } from 'termui/ai'; // conversation persistence
AI Components
For building AI-powered CLIs (streaming chat, tool calls, diffs). All in packages/components/src/ai/.
StreamingText (typography)
// Token-by-token streaming output with blinking cursor
<StreamingText text={partialResponse} cursor="▌" />
// From an async iterator
<StreamingText stream={tokenStream} => saveMessage(full)} />
// Typing animation for pre-buffered text
<StreamingText text={fullText} animate speed={30} />
Markdown with streaming
// Pass streaming prop — handles partial code fences and appends cursor
<Markdown streaming cursor="▌">
{partialMarkdownFromLLM}
</Markdown>
ChatThread + ChatMessage
<ChatThread maxHeight={30} autoScroll>
<ChatMessage role="user" name="You">
Explain React hooks
</ChatMessage>
<ChatMessage role="assistant" name="Claude" streaming>
<StreamingText text={response} cursor="▌" />
</ChatMessage>
<ChatMessage role="system" collapsed>
System prompt…
</ChatMessage>
</ChatThread>
// role: 'user'|'assistant'|'system'|'error'
// streaming prop shows ●●● typing indicator while true
ToolCall
<ToolCall
name="read_file"
args={{ path: '/src/index.ts' }}
status="running" // 'pending'|'running'|'success'|'error'
result={fileContents}
duration={230}
collapsible
/>
ThinkingBlock
<ThinkingBlock
content={thinkingTokens}
streaming
defaultCollapsed={true}
label="Reasoning"
tokenCount={1247}
/>
// Space/Enter to expand — shows "▶ Reasoning (1,247 tokens)" when collapsed
ToolApproval
<ToolApproval
name="run_command"
args={{ command: 'rm -rf node_modules' }}
risk="medium" // 'low'|'medium'|'high'
=> {}}
=> {}}
=> {}}
timeout={30} // auto-deny after N seconds
/>
// Keys: y approve, n deny, a always-allow
TokenUsage + ContextMeter
// Inline token/cost summary
<TokenUsage prompt={1200} completion={850} model="claude-sonnet-4-6" showCost />
// ⟨ 1.2k in / 850 out · $0.003 ⟩
// Context window progress bar
<ContextMeter used={45000} limit={200000} label="Context" showPercent warnAt={80} criticalAt={95} />
ModelSelector
<ModelSelector
models={[
{ id: 'claude-sonnet-4-6', name: 'Claude Sonnet', provider: 'anthropic', context: 200000 },
{ id: 'gpt-4o', name: 'GPT-4o', provider: 'openai', context: 128000 },
]}
selected={model}
showContext
groupByProvider
/>
FileChange
<FileChange
changes={[
{ path: 'src/index.ts', type: 'modify', diff: unifiedDiff },
{ path: 'src/utils.ts', type: 'create', content: newContent },
{ path: 'old.ts', type: 'delete' },
]}
=> applyChange(path)}
=> rejectChange(path)}
=> applyAll()}
/>
// Keys: ↑↓ navigate files, Enter expand/collapse, y accept, n reject
DiffView (data category)
<DiffView
oldText={original}
newText={modified}
filename="src/index.ts"
language="typescript"
mode="unified" // 'unified'|'split'|'inline'
context={3}
showLineNumbers
/>
termui/ai — provider-agnostic hooks
import { useChat, useCompletion, createConversationStore } from 'termui/ai';
// Full chat with history
const { messages, sendMessage, isStreaming, abort, error, tokenUsage } = useChat({
provider: 'anthropic', // 'anthropic'|'openai'|'ollama'|'custom'
model: 'claude-sonnet-4-6',
apiKey: process.env.ANTHROPIC_API_KEY,
system: 'You are a helpful assistant.',
onToolCall: async (call) => executeToolCall(call),
});
// Single-turn completion
const { complete, result, isStreaming } = useCompletion({ provider: 'anthropic', model: '...' });
// Conversation persistence
const store = createConversationStore({ dir: '~/.myapp/conversations', format: 'jsonl' });
await store.save(id, messages);
const history = await store.load(id);
Testing
import { renderToString, fireEvent, waitFor } from 'termui/testing';
const out = await renderToString(<Spinner label="Loading" />);
expect(out).toContain('Loading');
await fireEvent.keyPress({ upArrow: true });
await waitFor(() => expect(getRow()).toBe(1));
Key Constraints
- ESM-only —
"type": "module"required in package.json - Node.js 18+
- React 18 peer dependency
- Colors: Ink converts hex to ANSI;
NO_COLOR/FORCE_COLORenv vars respected useInputfires on every keystroke — guard withif (!isFocused) returnin multi-input UIs