Build terminal user interfaces in TypeScript.
TermUI is a TypeScript framework for building terminal apps. You get a layout engine, JSX support, React-style hooks, context, global state management, theming, animations, routing, and a hot-reload dev server. No curses bindings. No C extensions. Pure TypeScript.
npx create-termui-app my-app
cd my-app
npm install
npm run devnpm install @termuijs/core @termuijs/widgets @termuijs/jsximport { App } from '@termuijs/core'
import { Box, Text } from '@termuijs/widgets'
import { useState, useInput } from '@termuijs/jsx'
function Counter() {
const [count, setCount] = useState(0)
useInput((key) => {
if (key === '+') setCount((c) => c + 1)
if (key === 'q') app.exit()
})
return (
<Box border="round" padding={1}>
<Text bold>Count: {count}</Text>
<Text dim>Press + to increment · q to quit</Text>
</Box>
)
}
const app = new App(rootWidget)
await app.mount()Note: The JSX-based API above shows the component-based approach with
@termuijs/jsx. See the Quick Start for the widget-tree approach.
| Package | What it does |
|---|---|
@termuijs/core |
Screen buffer, input parsing, event system, flexbox layout |
@termuijs/widgets |
Box, Text, Table, ProgressBar, Spinner, Gauge, VirtualList, and more |
@termuijs/ui |
Select, Tabs, Modal, Toast, Tree, MultiSelect, CommandPalette |
@termuijs/jsx |
JSX runtime — useState, useEffect, useRef, useContext, useAsync, memo() |
@termuijs/store |
Zustand-like global state with selector subscriptions |
@termuijs/testing |
In-memory test renderer: render, query, fireKey, assert |
@termuijs/tss |
Terminal Style Sheets — CSS-like theming with variables and pseudo-classes |
@termuijs/motion |
Spring-physics and easing-based animations |
@termuijs/router |
File-based screen routing with typed params and guards |
@termuijs/data |
Real-time system data: CPU, memory, disk, processes |
@termuijs/dev-server |
Hot-reload dev server — restart on file save in <200ms |
@termuijs/quick |
Fluent builder API — build dashboards in ~20 lines |
create-termui-app |
Project scaffolding CLI |
Share state across your component tree without prop drilling — works identically to React's context API:
import { createContext, useContext } from '@termuijs/jsx'
const ThemeCtx = createContext({ primary: '#00ff88', bg: '#0a0a0f' })
function App() {
return (
<ThemeCtx.Provider value={theme}>
<Dashboard />
</ThemeCtx.Provider>
)
}
function StatusBar() {
const { primary } = useContext(ThemeCtx)
return <Text color={primary}>Ready</Text>
}Zustand-like state management with selector-based subscriptions. Only re-renders the components that need to:
import { createStore } from '@termuijs/store'
const useAppStore = createStore((set) => ({
count: 0,
increment: () => set((s) => ({ count: s.count + 1 })),
}))
function Counter() {
const count = useAppStore((s) => s.count)
const increment = useAppStore((s) => s.increment)
useInput((key) => { if (key === '+') increment() })
return <Text>Count: {count}</Text>
}Renders only visible rows. A list of 1,000,000 items is as fast as a list of 10:
import { VirtualList } from '@termuijs/widgets'
const list = new VirtualList({
totalItems: 1_000_000,
renderItem: (index) => `Log line ${index}: some content`,
onSelect: (index) => openDetail(index),
})
app.events.on('key', (e) => {
if (e.key === 'up') list.selectPrev()
if (e.key === 'down') list.selectNext()
})Write fast, headless component tests without a real terminal:
import { render } from '@termuijs/testing'
const t = render(<Counter />)
expect(t.getByText('Count: 0')).toBeTruthy()
t.fireKey('+')
expect(t.getByText('Count: 1')).toBeTruthy()
t.unmount()memo() skips re-renders when props haven't changed. Batching collapses multiple setState calls into one render automatically:
import { memo } from '@termuijs/jsx'
const ProcessRow = memo(function ProcessRow({ pid, name, cpu }) {
return <Text>{pid} {name} {cpu}%</Text>
})
// ProcessRow only re-renders when its props actually changeReal process-based hot reload — your app restarts in <200ms on every save:
npm run dev # uses create-termui-app setup
# or directly:
npx termui-dev --entry src/index.tsxApplication Layer: @termuijs/ui · @termuijs/quick · create-termui-app
Component Layer: @termuijs/widgets · @termuijs/jsx · @termuijs/store
Testing: @termuijs/testing
Feature Layer: @termuijs/tss · @termuijs/router · @termuijs/motion
Core Layer: @termuijs/core · @termuijs/data
Every layer depends only on the layers below it. You can use any package independently.
import { app, gauge, table } from '@termuijs/quick'
import { cpu, memory, processes } from '@termuijs/data'
app('System Monitor')
.rows(
app.cols(
gauge('CPU', () => cpu.percent / 100),
gauge('MEM', () => memory.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 { createStore } from '@termuijs/store'
const useTodoStore = createStore((set, get) => ({
todos: [] as string[],
addTodo: (text: string) => set((s) => ({ todos: [...s.todos, text] })),
removeTodo: (i: number) => set((s) => ({
todos: s.todos.filter((_, idx) => idx !== i)
})),
}))
function TodoApp() {
const todos = useTodoStore((s) => s.todos)
const addTodo = useTodoStore((s) => s.addTodo)
// Only re-renders when todos changes
}import { useAsync } from '@termuijs/jsx'
function ProcessList() {
const { data, loading, error, refetch } = useAsync(
() => fetchProcesses(),
[]
)
if (loading) return <Spinner label="Loading..." />
if (error) return <Text color="red">{error.message}</Text>
return <VirtualList totalItems={data.length} renderItem={(i) => data[i].name} />
}git clone https://github.com/Karanjot786/TermUI.git
cd TermUI
pnpm install
pnpm run build
# System monitor dashboard
cd examples/dashboard
npx tsx src/index.tsx
# JSX-based dashboard
cd examples/jsx-dashboard
npx tsx src/index.tsxFive examples included: dashboard, jsx-dashboard, showcase, system-monitor, and todo-app.
packages/
core/ Screen buffer, input, events, layout
widgets/ Base widget library (+ VirtualList)
ui/ High-level components
jsx/ JSX runtime, hooks, context, memo
store/ Global state management
testing/ Test renderer
tss/ Terminal Style Sheets
motion/ Spring animation engine
router/ File-based routing
data/ System data providers
dev-server/ Hot-reload dev server
quick/ Fluent builder API
create-termui-app/ Project scaffolding CLI
examples/
dashboard/ Real-time system monitor
jsx-dashboard/ JSX-based dashboard
showcase/ Widget gallery
system-monitor/ Advanced monitor
todo-app/ Interactive todo list
pnpm test # Run all 356 tests
pnpm run build # Build all 13 packages- Node.js 18+
- A terminal with TTY support (256-color or truecolor recommended)
- Fork the repo
- Create a branch:
git checkout -b feature/my-feature - Install:
pnpm install - Build:
pnpm run build - Test:
pnpm test - Submit a pull request
MIT