High Quality Terminal UI
btop-grade dashboards with a one-import API, dark by default, zero runtime dependencies.
TypeScript, Rust, Go, Python, Zig, C++, Ruby, PHP and Perl.
hqtui.com · npm · docs · discussions
Every screenshot on this page is a real frame from hqtui-demo, captured at 2x.
Run it yourself on whichever runtime you already have:
bunx @profullstack/hqtui-demo # Bun
npx @profullstack/hqtui-demo # Node 22.6+
deno run -A npm:@profullstack/hqtui-demo # Deno 2The same ten-screen demo runs natively in eight other languages, one command each and no checkout: see See it running.
Add --sim to any of them for a deterministic simulation instead of your real machine.
Terminal apps do not have to look like 1990s ncurses software. HQTUI owns the terminal directly — ANSI/VT sequences, a typed-array framebuffer, differential rendering, Braille graphics and truecolor — so a dashboard can look and feel like a modern desktop app while starting instantly and running fine over SSH.
No ncurses. No browser DOM. No React. No native addon. No network access. Ever.
bun add @profullstack/hqtui # Bun is the default runtime
npm add @profullstack/hqtui # Node 22.6+ works too
deno add npm:@profullstack/hqtui # Deno 2The API below is the TypeScript reference implementation. Rust, Go, Python, Zig, C++, Ruby, PHP and Perl each have their own idiomatic API over the same rendering model: see Other languages.
import { createApp } from "@profullstack/hqtui";
const app = await createApp();
app.render(({ ui }) => {
ui.panel({ title: "Hello" }, (panel) => {
panel.text("Hello, terminal.");
});
});
await app.start();That is the whole API surface you need to start. createApp() already gives you a dark
theme, truecolor with automatic 256/16-colour fallback, mouse tracking, the alternate
screen, resize handling, 30fps adaptive rendering (15 over SSH), and a terminal that is
restored no matter how the process dies — Ctrl+C, SIGTERM, or an uncaught exception.
import { createApp } from "@profullstack/hqtui";
const app = await createApp({ fps: 30 });
app.render(({ ui, theme }) => {
ui.grid({ columns: ["2fr", "1fr"], rows: [14, "1fr"], gap: 1 }, (grid) => {
grid.panel({ title: "CPU" }, (p) => {
p.graph({ values: cpuHistory, min: 0, max: 100, fill: true });
p.meters(cores.map((value, i) => ({ label: `P${i}`, value })), { columns: 2 });
});
grid.panel({ title: "Memory" }, (p) => {
p.meter({ label: "Used", value: 0.42, text: "6.7 GiB" });
p.keyValues([{ label: "Cached", value: "4.0 GiB" }]);
});
grid.panel({ title: "Processes", colSpan: 2 }, (p) => {
p.table({
rows: processes,
columns: [
{ key: "pid", title: "PID", width: 7, align: "right" },
{ key: "name", title: "Name" },
{ key: "cpu", title: "CPU%", width: 6, align: "right" },
],
});
});
});
});
await app.start();Ten screens. Real metrics on Linux, macOS and Windows, with no native dependencies.
From npm, on any JavaScript runtime you already have:
bunx @profullstack/hqtui-demo # Bun
npx @profullstack/hqtui-demo # Node 22.6+
deno run -A npm:@profullstack/hqtui-demo # Deno 2Every language runs that same demo through one launcher. It fetches current
main into a private cache, prints the exact commit it built, and leaves your
checkout alone. It is a shell script from the internet, so
read it first:
curl -fsSL https://hqtui.com/demo.sh | sh -s -- --system typescript
curl -fsSL https://hqtui.com/demo.sh | sh -s -- --system rust
curl -fsSL https://hqtui.com/demo.sh | sh -s -- --system go
curl -fsSL https://hqtui.com/demo.sh | sh -s -- --system python
curl -fsSL https://hqtui.com/demo.sh | sh -s -- --system zig
curl -fsSL https://hqtui.com/demo.sh | sh -s -- --system cpp
curl -fsSL https://hqtui.com/demo.sh | sh -s -- --system ruby
curl -fsSL https://hqtui.com/demo.sh | sh -s -- --system php
curl -fsSL https://hqtui.com/demo.sh | sh -s -- --system perl--system uses the toolchain you already have installed. Swap it for --mise
and the launcher supplies a pinned one instead, so nothing but mise has to be
on the box. Demo arguments pass straight through: --sim for a deterministic
simulation, --snapshot for headless text where there is no TTY, and
--screen traffic to open on one tab.
From a checkout, mise run demo:<language> is the same thing, and
mise run demo-local:<language> runs your local edits without updating first.
Or run the examples directly:
bun apps/demo/src/main.ts # TypeScript
cd ports/rust && cargo run --example dashboard # Rust
cd ports/go && go run ./examples/dashboard # Go
cd ports/python && python examples/dashboard.py # Python
cd ports/zig && zig build run-dashboard # ZigC++, Ruby, PHP and Perl build a native library first; their READMEs have the CMake invocation.
Live sockets grouped by protocol, TCP/UDP/ICMP counters with retransmit rate, HTTP request tracking parsed from nginx/apache access logs (req/s, status mix, top paths, WebSocket upgrades), and sshd authentication events from the journal.
Active sessions from who, login history from wtmp, failed attempts from btmp, and a
process state breakdown.
systemd units with failures first, docker containers, kernel counters (context switches, interrupts, forks, entropy, open file descriptors) and filesystems with inode usage.
Nothing above needs root. Running with sudo additionally unlocks socket process
names, failed logins, HTTP access logs, per-process I/O and the full journal — the
demo tells you which of those it could not read.
| Layout | rows, columns, grid with spans, "40%", "2fr", auto, min/max, padding, gaps, clipping, responsive breakpoints |
| Widgets | panel, table, tree, list, log viewer, key/values, meter, gauge, donut, progress, sparkline, line/area/multi-series graph, histogram, heat bar, tabs, status bar, button, checkbox, toggle, radio, select, text input, modal, command palette, tooltip, badge, divider |
| Graphics | Braille canvas (2×4 pixels per cell), block/half-block/quadrant/ASCII modes, gradients, software alpha blending |
| Color | 24-bit truecolor, automatic 256 and 16-colour quantization, NO_COLOR, monochrome and high-contrast modes |
| Themes | dark (default), dracula, nord, tokyo night, gruvbox, matrix, monochrome, high contrast, light — plus defineTheme() |
| Input | normalized keys with modifiers, SGR mouse (click, drag, scroll, move), bracketed paste, focus events, Tab focus traversal |
| Testing | headless renderer: renderToText, renderToScreen, renderToAnsi, renderToHtml — no TTY required |
Terminal apps are usually untestable. Here they are not:
import { renderToScreen } from "@profullstack/hqtui";
const screen = renderToScreen(({ ui }) => ui.panel({ title: "CPU" }, (p) => p.text("72%")), {
width: 40,
height: 6,
});
expect(screen.contains("72%")).toBe(true);
expect(screen.cell(2, 0).fg).toBe(theme.title);The screen is one grid of cells in four typed arrays — no object is allocated per cell. Each frame is diffed against the previous one and only the changed runs are written, with a model of the terminal's pen so no redundant escape sequence is emitted.
Changing CPU 72% to CPU 73% writes a single character, not a screen.
bun run bench160x50 (8,000 cells) · bun 1.4 · linux x64
renderer.frame.unchanged 0.068ms no output written
renderer.frame.1pct 0.140ms 627 bytes/frame
renderer.frame.10pct 0.291ms 2,639 bytes/frame
renderer.frame.100pct 1.409ms 8,341 bytes/frame
widgets.dashboard 0.425ms 6 panels, layout + widgets + diff
packages/hqtui the library
apps/demo the reference dashboard (real + simulated data)
apps/web hqtui.com
examples/ small, focused programs
ports/ Rust, Go, Python, Zig, C, C++ and the Ruby/PHP/Perl bindings
docs/ the original PRD
hqtui is not a TypeScript library that grew wrappers. Nine languages run the same ten screens, and they stay honest against each other through a shared corpus of golden fixtures — thirteen groups, roughly nine thousand reference cells. The same widget arguments must produce the same cells, the same colors and the same escape bytes, byte for byte.
| Language | What it is | Runtime deps | Docs |
|---|---|---|---|
| TypeScript | the reference implementation | none | packages/hqtui |
| Rust | native port | standard library only | README |
| Go | native port | standard library only | README |
| Python | native port | standard library only | README |
| Zig | native port | standard library only | README |
| C++ | native demo on the C core; library API experimental | none | README |
| Ruby | binding over the shared C ABI | the native engine | README |
| PHP | binding over the shared C ABI | the native engine | README |
| Perl | binding over the shared C ABI | the native engine | README |
The four native ports are ports, not bindings: no Node is in the picture at runtime or at build time, and each is idiomatic in its own language rather than a transliteration. The C11 core in ports/c is still in development and has no demo of its own yet.
ports/README.md has the details, and ports/TARGETS.md is the scored list of which language is next.
Questions, showcases and bug reports have a home at
bbs.hqtui.com — a board running
tsbb, themed to match this project. It
serves no client-side JavaScript, and tsbb-tui reads and posts to it from a
terminal, which felt like the right place for a terminal UI library to hold its
conversations.
For the TypeScript implementation, Bun is the default. Node 22.6+ runs everything unchanged (it strips TypeScript natively).
Deno 2 runs the library and the demo through its npm compatibility layer (deno run -A);
it is exercised by hand rather than in CI. Tested on Linux, macOS and Windows Terminal; degrades
gracefully on limited terminals (no mouse, quantized color, ASCII instead of Braille).
MIT.







