Introducing `terminui` 📟
A fast, double buffered toolkit for building terminal UIs.
I wanted a TUI toolkit with a small core model, predictable double buffered rendering (only changed cells flush), and strict types.
Built with Command Code.
Core pipeline:
Backend -> Terminal -> Frame -> Buffer -> Cells
Widgets are pure render functions:
`(area: Rect, buf: Buffer) => void`
No classes, no `this`, no hidden mutable UI tree.
$ pnpm add terminui
$ npm i terminui
Frame loop is intentionally minimal:
1) render into current buffer
2) diff previous vs current (`bufferDiff`)
3) draw only changed cells
4) flush
5) swap buffers
6) clear next write buffer
So terminal I/O scales with what changed, not with full screen size every frame.
Cell model is tiny but expressive:
- `symbol`
- `fg` / `bg` / optional `underlineColor`
- `modifier` bitmask
Diffs are structural (symbol colors modifiers), so unchanged cells never hit the draw path.
Wide-char correctness is built in (CJK/fullwidth).
`charWidth(codePoint)` detects width-2 glyphs, and writes a placeholder empty cell after them, which keeps cursor math, wrapping, and diffs stable across frames.
Layout uses a small constraint solver:
`Length`, `Percentage`, `Ratio`, `Min`, `Max`, `Fill`
`splitLayout` does two passes:
- resolve non-fill constraints first
- distribute remaining space by fill weights
Then leftover cells go to the last fill chunk for deterministic results.
Style system:
- named ANSI colors indexed(0..255) 24-bit RGB
- modifier bitflags (bold, italic, underline, etc)
- additive/subtractive modifier composition via `patchStyle`
This makes style composition functional and predictable.
Built-in widgets:
Block, Paragraph, List, Table, Gauge, LineGauge, Tabs, Sparkline, BarChart, Scrollbar, Clear
Stateful renderers exist where they should (selection/offset state), while rendering stays pure.
Backend contract is deliberately tiny:
`size`, `draw`, `flush`, `clear`, cursor get/set hide/show
So you can swap in a Node backend, a test backend, or custom transport without changing widget code.
Examples include:
- primary-screen dashboard
- alternate-screen multi-frame simulation
- kitchen-sink demo (all widgets constraints styles)
- live and one-shot weather dashboard (Open-Meteo)
Type safety baseline:
`strict: true`, `noUncheckedIndexedAccess: true`, zero `any`.
If you build TUIs in TS and want composable primitives, try it:
$ pnpm add terminui
$ npm i terminui
Build something and share with me. :)