Build terminal applications in TypeScript.
⭐ GSSoC 2026 contributors: star this repo before opening a PR. The
star-checkworkflow blocks unstarred merges. Read CONTRIBUTING.md for the full point system.
📖 Docs site: API docs, guides, and examples live at termui.io. The source is at Karanjot786/TermUI_Docs.
TermUI is a TypeScript framework for building terminal apps. You get a layout engine, JSX support, React-style hooks, focus management, global state, theming, animations, routing, real-time data, error boundaries, and a hot-reload dev server. No curses bindings. No C extensions. Pure TypeScript.
- Bun >= 1.3.0
bunx create-termui-app my-app
cd my-app
bun install
bun run devRequires Bun 1.3.0 or newer and Node.js 18.0.0 or newer.
bun add @termuijs/core @termuijs/widgets @termuijs/jsximport { App } from '@termuijs/core'
import { Box, Text } from '@termuijs/widgets'
import { useState, useKeymap, ErrorBoundary } from '@termuijs/jsx'
function Counter() {
const [count, setCount] = useState(0)
useKeymap({
'+': () => setCount((c) => c + 1),
'ctrl+c': () => process.exit(0),
})
return (
<Box border="round" padding={1}>
<Text bold>Count: {count}</Text>
<Text dim>Press + to increment, ctrl+c to quit</Text>
</Box>
)
}
render(
<ErrorBoundary fallback={(e) => <Text color="red">{e.message}</Text>}>
<Counter />
</ErrorBoundary>
)| Package | What it does |
|---|---|
@termuijs/core |
Screen buffer, input parsing, event system, flexbox layout, caps flags, WCAG utilities |
@termuijs/widgets |
40+ widgets: Box, Text, Table, VirtualList, StreamingText, ChatMessage, ToolCall, Card, Sidebar, LineChart, HeatMap, and more |
@termuijs/ui |
Select, Tabs, Modal, Toast, NotificationCenter, imperative prompts, PasswordInput, NumberInput, PathInput |
@termuijs/jsx |
JSX runtime with useState, useEffect, useKeymap, useMotion, ErrorBoundary, and a complete focus system |
@termuijs/store |
Zustand-style global state with selector subscriptions and batch updates |
@termuijs/testing |
In-memory test renderer: render, query, fireKey, waitFor, renderToString |
@termuijs/tss |
Terminal Style Sheets with variables, pseudo-classes, AutoThemeProvider, and useTheme |
@termuijs/motion |
Spring-physics and easing animations; respects NO_MOTION |
@termuijs/router |
File-based screen routing with typed params, guards, and ErrorBoundary wrapping |
@termuijs/data |
Real-time system data: CPU, memory, disk, network, processes; raw API and reactive hooks |
@termuijs/dev-server |
Hot-reload dev server with graceful restart in under 200ms |
@termuijs/quick |
Fluent builder API for dashboards in ~20 lines |
create-termui-app |
Project scaffolding CLI |
Declare key bindings as a map. Cleaner than chained if-statements. Multiple calls in one component are additive.
import { useKeymap } from '@termuijs/jsx'
function App() {
useKeymap({
'ctrl+c': () => process.exit(0),
'ctrl+s': () => save(),
'/': () => openSearch(),
'?': () => showHelp(),
})
return <Box>...</Box>
}Four hooks for building keyboard-accessible interfaces:
import { useFocusManager, useFocus, useFocusTrap, useKeyboardNavigation } from '@termuijs/jsx'
// App root: owns global focus state
function App() {
const { FocusContext, focus, blur, focused } = useFocusManager()
return (
<FocusContext.Provider value={{ focus, blur, focused }}>
<Screen />
</FocusContext.Provider>
)
}
// Modal: Tab key stays inside
function Modal({ fieldIds }) {
useFocusTrap(fieldIds)
return <Box>...</Box>
}
// List: standard arrow key navigation
function List({ items }) {
const { selectedIndex } = useKeyboardNavigation({ items, loop: true })
return <Box>...</Box>
}Wrap any subtree. Errors show a fallback instead of crashing the app.
import { ErrorBoundary } from '@termuijs/jsx'
<ErrorBoundary fallback={(err) => <Text color="red">Error: {err.message}</Text>}>
<Dashboard />
</ErrorBoundary>TermUI checks the terminal environment before rendering unicode or animations. Set environment variables to get ASCII output in CI or reduced-motion output for accessibility.
NO_UNICODE=1 # ASCII fallbacks for all widgets
NO_MOTION=1 # Skip all animations; static output
NO_COLOR=1 # Disable ANSI color sequencesimport { caps } from '@termuijs/core'
const bullet = caps.unicode ? '●' : '*'import { NotificationCenter, useNotifications } from '@termuijs/ui'
function App() {
return (
<Box>
<NotificationCenter position="top-right" />
<Dashboard />
</Box>
)
}
function Dashboard() {
const { notify } = useNotifications()
useKeymap({
's': async () => {
await save()
notify('Saved', { type: 'success', duration: 2000 })
},
})
return <Box>...</Box>
}Ask for user input without restructuring your component tree.
import { prompt } from '@termuijs/ui'
const ok = await prompt.confirm('Delete all logs?')
if (ok) deleteLogs()
const name = await prompt.text('Enter a name:')
const env = await prompt.select('Choose environment:', ['dev', 'staging', 'prod'])import { createStore, batch } from '@termuijs/store'
const useAppStore = createStore((set) => ({
count: 0,
label: '',
increment: () => set((s) => ({ count: s.count + 1 })),
}))
// Multiple updates; one render pass
batch(() => {
useAppStore.setState({ count: 10 })
useAppStore.setState({ label: 'done' })
})import { AutoThemeProvider, useTheme } from '@termuijs/tss'
import { tokensToTSS, nordTheme } from '@termuijs/tss'
// Detect terminal background and pick the closest theme
<AutoThemeProvider fallback="dracula">
<App />
</AutoThemeProvider>
// Switch at runtime
const { theme, setTheme } = useTheme()
setTheme('catppuccin')
// Use token objects directly
const primary = nordTheme['--primary']Display AI tool usage and streaming output with purpose-built widgets:
import { StreamingText, ChatMessage, ToolCall } from '@termuijs/widgets'
<ChatMessage role="assistant" content="Here is the result..." />
<ToolCall name="read_file" status="running" args={{ path: '/etc/hosts' }} />
<StreamingText text="Generating response..." speed={30} />Renders only visible rows. A list of 1,000,000 items performs the same as a list of 10.
const list = new VirtualList({
totalItems: 1_000_000,
renderItem: (index) => `Log line ${index}`,
onSelect: (index) => inspect(index),
})import { useCpu, useMemory, useHttpHealth } from '@termuijs/data'
function Dashboard() {
const cpu = useCpu(500)
const mem = useMemory(1000)
const checks = useHttpHealth(['https://api.example.com/health'], 5000)
return (
<Box flexDirection="column">
<Text>CPU: {cpu.usage.toFixed(1)}%</Text>
<Text>MEM: {(mem.used / mem.total * 100).toFixed(1)}%</Text>
</Box>
)
}import { render } from '@termuijs/testing'
const t = render(<Counter />)
t.fireKey('+')
expect(t.getByText('Count: 1')).toBeTruthy()
// Async assertions
await t.waitFor(() => {
expect(t.getByText('Loaded')).toBeTruthy()
})
// ANSI-free snapshot
expect(t.renderToString()).toContain('Count: 1')
t.unmount()Application Layer: @termuijs/ui · @termuijs/quick · create-termui-app
Component Layer: @termuijs/widgets · @termuijs/jsx · @termuijs/store · @termuijs/testing
Feature Layer: @termuijs/tss · @termuijs/router · @termuijs/motion
Core Layer: @termuijs/core · @termuijs/data
Dev tooling: @termuijs/dev-server
Every layer depends only on the layers below it. Use any package independently.
import { app, gauge, table } from '@termuijs/quick'
import { useCpu, useMemory } from '@termuijs/quick'
app('System Monitor')
.rows(
app.cols(
gauge('CPU', () => useCpu().usage / 100),
gauge('MEM', () => useMemory().percent / 100),
),
table('Processes', {
columns: ['Name', 'PID', 'CPU%'],
data: () => processes.top(10).map((p) => ({
Name: p.name,
PID: p.pid,
'CPU%': p.cpu.toFixed(1),
})),
}),
)
.run()import { app, chatMessage, toolCall, streamingText } from '@termuijs/quick'
app('AI Assistant')
.rows(
chatMessage({ role: 'user', content: 'Show me the top processes' }),
toolCall({ name: 'get_processes', status: 'running' }),
streamingText({ text: 'Fetching system data...', speed: 40 }),
)
.run()git clone https://github.com/Karanjot786/TermUI.git
cd TermUI
bun install
bun run buildTo run the AI streaming example:
cd examples/ai-streaming
bun run dev| Example | Purpose | Features demonstrated | Path |
|---|---|---|---|
| Auth Flow | Demonstrates authentication | Store, Text inputs, conditional rendering | examples/auth-flow |
| Todo App | Interactive todo list | Store batching, Lists, Inputs | examples/todo-app |
| Forms and Validation | Form validation | Form widget, inputs, modals | examples/forms-and-validation |
| Multi-screen Router | Screen routing | Router, nested screens, navigation | examples/multi-screen-router |
| Dashboard | Real-time system monitor | Quick API, layout, data hooks | examples/dashboard |
| System Monitor | Advanced monitor | Complex layouts, live charts | examples/system-monitor |
| JSX Dashboard | JSX-based dashboard | JSX runtime | examples/jsx-dashboard |
| Showcase | Widget gallery | Various display widgets | examples/showcase |
| Widget Gallery | All widgets in one place | Comprehensive widget showcase | examples/widget-gallery |
| CLI Wrapper | Live log streaming | Subprocesses, streaming output | examples/cli-wrapper-live |
| AI Assistant | Interactive AI chat with streaming | ChatMessage, StreamingText, ToolCall, ToolApproval, useAI, dual-mode operation | examples/ai-assistant |
packages/
core/ Screen buffer, input, events, layout, caps flags
widgets/ Widget library (40+ widgets)
ui/ High-level components and inputs
jsx/ JSX runtime, hooks, focus system, ErrorBoundary
store/ Global state management
testing/ Test renderer
tss/ Terminal Style Sheets and theming
motion/ Spring animation engine
router/ File-based routing
data/ System data providers and hooks
dev-server/ Hot-reload dev server
quick/ Fluent builder API
create-termui-app/ Project scaffolding CLI
examples/
ai-streaming/ Mock AI chat with StreamingText, ChatMessage, ToolCall
auth-flow/ Authentication flow example
cli-wrapper-live/ Live subprocess log streaming
dashboard/ Real-time system monitor
forms-and-validation/ Multi-field form with validation
jsx-dashboard/ JSX-based dashboard
multi-screen-router/ Multi-screen router example
showcase/ Widget gallery
system-monitor/ Advanced monitor
todo-app/ Interactive todo list
widget-gallery/ All widgets in one place
Note: This project uses Bun as its package manager. Install Bun first: https://bun.sh/docs/installation
bun install
bun run build # Build all 14 packages
bun run test # Run all 1394 tests
bun run typecheck # Type-check all packagesSee DEVELOPMENT.md for local setup, build commands, testing, and contributor workflow.
- Bun 1.3.0 or newer for development
- Node.js 18+ to run published
@termuijs/*packages from npm - A terminal with TTY support (256-color or truecolor recommended)
See ROADMAP.md for current waves, difficulty breakdown, and future plans. Pick work that fits your skill level.
- Discussions: ask questions, propose ideas, share what you built
- Wiki: roadmap, architecture, contribution workflow, FAQ
- Project board: track issues and PRs in progress
- Issues: report bugs, claim tasks
- Docs: API reference, guides, examples
Thank you to everyone who has contributed to TermUI. Every bug fix, feature, test, and doc improvement makes this project better for everyone who builds with it.
Want to join this list? Check the open issues and read CONTRIBUTING.md to get started.
MIT
If you encounter runtime rendering issues or stale execution logs while working with cross-package scripts inside this monorepo ecosystem, clear your local project configurations and refresh your setups completely by running:
bun run clean
bun install