Widgets
Every widget HQTUI draws, what it actually looks like, and the code that drew it. The pictures are not screenshots: each one is rendered headlessly at build time by the library itself, and each snippet is a region of a program that compiles and runs. If a widget stopped drawing, this page would go blank rather than lie.
| Language | Widgets | Gallery |
|---|---|---|
| TypeScript | 31 / 31 | examples/widgets/gallery.ts |
| JavaScript | 31 / 31 | examples/widgets/gallery.js |
| Rust | 31 / 31 | ports/rust/examples/widgets.rs |
| Go | 31 / 31 | ports/go/examples/widgets/main.go |
| Python | 31 / 31 | ports/python/examples/widgets.py |
| Zig | 31 / 31 | ports/zig/examples/widgets.zig |
| C++ | 31 / 31 | ports/cpp/examples/widgets.cpp |
| Ruby | 31 / 31 | ports/ruby/examples/widgets.rb |
| PHP | 31 / 31 | ports/php/examples/widgets.php |
| Perl | 31 / 31 | ports/perl/examples/widgets.pl |
| COBOL | 31 / 31 | ports/cobol/examples/widgets.cbl |
Text
Text
textTextAligned, styled, optionally wrapped copy. Every other widget is built on it.
Plain text. It fills the width it is given. Bold, in the theme's primary color. Right aligned. Long copy wraps when you ask it to, instead of being cut at the edge.
export function text(ui: Container, theme: Theme): void {
ui.text("Plain text. It fills the width it is given.");
ui.text("Bold, in the theme's primary color.", { bold: true, fg: theme.primary });
ui.text("Right aligned.", { align: "right" });
ui.text("Long copy wraps when you ask it to, instead of being cut at the edge.", { wrap: true });
}Label
labelTextText in the theme's muted color: captions and the line under a number.
cpu · 8 cores · 3.4 GHz 42.1% 15 minute average
export function label(ui: Container, theme: Theme): void {
// `label` is `text` in the theme's muted color: secondary copy, captions,
// the line under a number that says what the number is.
ui.label("cpu · 8 cores · 3.4 GHz");
ui.text("42.1%", { bold: true, fg: theme.success });
ui.label("15 minute average");
}Heading
headingTextBold text in the theme's title color, for sectioning without a panel.
Storage Four volumes, one degraded Network
export function heading(ui: Container, theme: Theme): void {
// `heading` is `text` in the theme's title color, bold.
ui.heading("Storage");
ui.label("Four volumes, one degraded");
ui.spacer(1);
ui.heading("Network", { fg: theme.accent });
}Badge
badgeTextA status chip. Filled, subtle or outline.
active idle failed
export function badge(ui: Container, theme: Theme): void {
ui.row({ size: 1, gap: 1 }, (r) => {
r.badge({ text: "active", color: theme.success, size: 10 });
r.badge({ text: "idle", color: theme.warning, variant: "subtle", size: 8 });
r.badge({ text: "failed", color: theme.danger, variant: "outline", size: 10 });
r.spacer("fill");
});
}Divider
dividerTextA rule, optionally labelled, that titles a section cheaply.
Above the line ────────────────────────────────────────────────────────── Below it ───────────────────────── status ───────────────────────── A labelled divider titles a section without spending a pa…
export function divider(ui: Container, theme: Theme): void {
ui.text("Above the line");
ui.divider();
ui.text("Below it");
ui.divider({ label: "status", align: "center", color: theme.accent });
ui.text("A labelled divider titles a section without spending a panel on it");
}Key/values
keyValuesTextAligned label and value pairs. The backbone of every system panel.
Host web-01.iad Uptime 18d 04:12 Load 0.42 0.51 0.60 Established 1,284
export function keyValues(ui: Container, theme: Theme): void {
// The backbone of every "System" panel: labels left, values right.
ui.keyValues([
{ label: "Host", value: "web-01.iad" },
{ label: "Uptime", value: "18d 04:12" },
{ label: "Load", value: "0.42 0.51 0.60", color: theme.warning },
{ label: "Established", value: "1,284", color: theme.success },
]);
}Status bar
statusBarTextThe keybinding strip along the bottom, with a right-hand slot.
F1 Help F2 Theme F3 Filter ^K Palette q Quit 0.41ms 18…
export function statusBar(ui: Container, _theme: Theme): void {
// Usually the last thing drawn, pinned to the bottom row.
ui.statusBar({
items: [
{ key: "F1", label: "Help" },
{ key: "F2", label: "Theme" },
{ key: "F3", label: "Filter", active: true },
{ key: "^K", label: "Palette" },
{ key: "q", label: "Quit" },
],
right: [{ label: "0.41ms 184 cells" }],
});
}Data
Table
tableDataColumns with alignment, widths, zebra striping, selection and scroll.
Name Size Type Modified src 4.2 KB dir 2m ago test 1.1 KB dir 5m ago package.json 1.2 KB file 10m ago README.md 3.4 KB file 1h ago
export function table(ui: Container, theme: Theme): void {
ui.table({
rows: [
{ name: "src", size: "4.2 KB", type: "dir", modified: "2m ago" },
{ name: "test", size: "1.1 KB", type: "dir", modified: "5m ago" },
{ name: "package.json", size: "1.2 KB", type: "file", modified: "10m ago" },
{ name: "README.md", size: "3.4 KB", type: "file", modified: "1h ago" },
],
selected: 1,
zebra: true,
columns: [
{ key: "name", title: "Name", min: 12, color: theme.primary },
{ key: "size", title: "Size", width: 9, align: "right" },
{ key: "type", title: "Type", width: 6 },
{ key: "modified", title: "Modified", width: 10, align: "right", color: theme.muted },
],
});
}List
listDataA selectable list with bullets and its own scrollbar.
▸ apps/demo ▸ packages/hqtui ▸ apps/web ▸ docs
export function list(ui: Container, theme: Theme): void {
ui.list({
items: [
{ label: "apps/demo", color: theme.primary },
{ label: "packages/hqtui" },
{ label: "apps/web" },
{ label: "docs" },
],
selected: 0,
bullet: "▸",
scrollbar: true,
});
}Tree
treeDataNested rows with expand state and per-node value columns.
└─ systemd 1.3 ├─ bash 0.1 ├─ bun 32.8 │ └─ bun:worker 12.4 └─ postgres 6.7
export function tree(ui: Container, _theme: Theme): void {
ui.tree({
nodes: [
{
label: "systemd",
expanded: true,
values: [{ text: "1.3", width: 6 }],
children: [
{ label: "bash", values: [{ text: "0.1", width: 6 }] },
{
label: "bun",
expanded: true,
values: [{ text: "32.8", width: 6 }],
children: [{ label: "bun:worker", values: [{ text: "12.4", width: 6 }] }],
},
{ label: "postgres", values: [{ text: "6.7", width: 6 }] },
],
},
],
selected: 2,
});
}Log
logDataLevelled log lines that tail by default and scroll back from the end.
12:45:02 INFO listening on :8080 12:45:09 WARN slow query 412ms {table=users} 12:45:11 ERROR upstream timeout 12:45:14 INFO retry succeeded
export function log(ui: Container, _theme: Theme): void {
ui.log({
entries: [
{ time: "12:45:02", level: "info", message: "listening on :8080" },
{ time: "12:45:09", level: "warn", message: "slow query 412ms", meta: "{table=users}" },
{ time: "12:45:11", level: "error", message: "upstream timeout" },
{ time: "12:45:14", level: "info", message: "retry succeeded" },
],
// Lines scrolled back from the newest. 0 keeps it tailing.
fromEnd: 0,
scrollbar: true,
});
}Scrollbar
scrollbarDataA bar over state you own, on any of the four edges, for anything that scrolls.
A scrollbar you drive yourself. It has no │ idea what is beside it, only how much there █ is, how much fits, and where you are. │ │
export function scrollbar(ui: Container, theme: Theme): void {
// The bar is over state you own, so it works beside anything that scrolls:
// wrapped prose, a canvas, a draw() of your own.
ui.row({ gap: 1 }, (r) => {
r.text(
"A scrollbar you drive yourself. It has no idea what is beside it, only " +
"how much there is, how much fits, and where you are.",
{ wrap: true, fg: theme.foreground },
);
r.scrollbar({ total: 40, viewport: 5, offset: 12, size: 1 });
});
}Calendar
calendarDataA month as a grid, with per-day styling. The dates are arithmetic, not a host calendar.
September 2026 Mo Tu We Th Fr Sa Su 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
export function calendar(ui: Container, theme: Theme): void {
// The dates are arithmetic, not a host calendar: every port has a different
// Date and none of them is consulted.
ui.calendar({
year: 2026,
month: 9,
selected: 8,
marks: [{ day: 15, color: theme.warning }, { day: 22, color: theme.success, bold: true }],
});
}Meters
Chart
chartMetersArbitrary (x, y) data with a domain on both axes. Lines, scatters and bars.
10 ⢀ ⠤⢄⣀⣀ ⢀⡠⠊⠁⠑⢄⡀ ⠉⠉⠒⠒⠢⠤⠤⣀⣀ ⡠⠔⠁ ⠈⠢⡀ ⢀⠔⠉⠒⠢⢄⠉⠉⠑⠒⠒⠤⠤⣀⣀⡀ ⣀⠔⠉ ⠈⠢⣀ ⡠⠊⠁ ⠉⠑⠢⠤⣀⡀ ⠈⠉⠉⠒⠒⠤⠤⢄⣀⣀ ⠑⢄ ⡠⠊ ⠈⠑⠒⠤⠒⠁ ⠉⠉⠒⠒⠢⠤⠤⣀⣀ ⢀⠔⠉ ⠉⠉⠑⠒ 0■ load ■ limit 0s 5s 10s
export function chart(ui: Container, theme: Theme): void {
// Points carry their own x, so a sparse series and a dense one line up.
ui.chart({
series: [
{ points: [[0, 1], [2, 6], [5, 3], [8, 9], [10, 4]], label: "load" },
{ points: [[0, 8], [10, 2]], label: "limit", color: theme.muted },
],
axis: true,
legend: true,
x: { min: 0, max: 10, ticks: 3, format: (v) => `${v}s` },
y: { min: 0, max: 10 },
});
}Meter
meterMetersA labelled bar. Smooth or segmented, heat-colored by default.
CPU ████████████████████████▊─────────────── 62% MEM ▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮ 31% SWP ██████████████████████████████████▊───── 87%
export function meter(ui: Container, theme: Theme): void {
ui.meter({ label: "CPU", value: 0.62, style: "smooth", color: theme.primary });
ui.meter({ label: "MEM", value: 0.31, style: "segmented" });
ui.meter({ label: "SWP", value: 0.87, style: "smooth" });
}Meters
metersMetersA whole bank of meters in one call, laid out in columns.
P0 ▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮ 12% P4 ▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮ 38% P1 ▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮ 44% P5 ▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮ 55% P2 ▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮ 71% P6 ▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮ 22% P3 ▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮ 9% P7 ▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮ 66%
export function meters(ui: Container, _theme: Theme): void {
// One call for a whole bank. `columns` lays them out side by side.
ui.meters(
[0.12, 0.44, 0.71, 0.09, 0.38, 0.55, 0.22, 0.66].map((value, i) => ({ label: `P${i}`, value })),
{ columns: 2, labelWidth: 4, valueWidth: 5, style: "segmented" },
);
}Progress
progressMetersA determinate bar that can show a count as well as a ratio.
Indexing █████████▉────────────────────── 37/120 Upload ██████████████████████████████▍────── 82%
export function progress(ui: Container, _theme: Theme): void {
ui.progress({ label: "Indexing", value: 37, max: 120, showCount: true });
ui.progress({ label: "Upload", value: 0.82 });
}Graph
graphMetersA Braille line chart, optionally filled under the curve.
⢀⣀⠤⣀ ⢀⡠⢄⡀▄ ▁⢀⠤⠒⠉⠁███⠉⠒⠤⡀ ⢀⡠⠊⠁▅█⠈⠒⠤⣀ ⡠⠒⠁██████████⠈⠑⠤⣀ ⢀⡠⠒⠉⠒⠢⠔⠁▃████████⠉⠒⠒⠉⠉████████████████▇⠉⠑⠒⠤⠤⠒⠒ ⢀⡠⠤⢄⣀⡠⠔⠊⠁▆▆███████████████████████████████████████████ ⣀⠤⠔⠊⠁▅████████████████████████████████████████████████████ ██████████████████████████████████████████████████████████
export function graph(ui: Container, theme: Theme): void {
// Braille line chart. `fill` shades the area under the curve.
ui.graph({ values: CPU_HISTORY, min: 0, max: 100, fill: true, color: theme.success, size: "1fr" });
}Sparkline
sparklineMetersOne row: label, inline chart and a value.
CPU ▁▂▃▂▃▅▄▆▇▅▄▅▆▇█▇▆▅▄▅ 44% Mem ▁▂▁▃▅▄█▇▅▃▂▄▆█▇▅ 31% Net ▁▂▃▂▃▅▄▆▇▅▄▅▆▇█▇▆▅▄▅ 2.4 MB/s
export function sparkline(ui: Container, theme: Theme): void {
ui.sparkline({ label: "CPU ", values: CPU_HISTORY, text: "44%", color: theme.success });
ui.sparkline({ label: "Mem ", values: NET_HISTORY, text: "31%", color: theme.warning });
ui.sparkline({ label: "Net ", values: CPU_HISTORY, text: "2.4 MB/s", color: theme.primary });
}Histogram
histogramMetersBlock columns. Cheaper than Braille and easier to read when short.
▃█▁ ▆ ▃███ ▆█▃ ████▅ ▇▂███▃▇█████▆▁▄ ▄███████████████ ▇▄████████████████ ▃███████████████████ ████████████████████
export function histogram(ui: Container, theme: Theme): void {
// Block columns. Cheaper than Braille and easier to read when short.
ui.histogram({ values: CPU_HISTORY, color: theme.accent, size: "1fr" });
}Heat bar
heatBarMetersA segmented bar colored along the theme's heat ramp.
▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮ ▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮ ▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮
export function heatBar(ui: Container, _theme: Theme): void {
// Segmented bar colored along the theme's heat ramp, like btop's temperatures.
ui.heatBar({ value: 0.28 });
ui.heatBar({ value: 0.64 });
ui.heatBar({ value: 0.91 });
}Gauge
gaugeMetersA semicircular dial. Wants at least nine columns by five rows.
⢀⣠⣤⡶⠶⠞⠛⠛⠛⠛⠛⠛⠛⠶⠶⣦⣤⣀ ⢀⣠⡶⠞⠋⠉ ⠈⠉⠛⠷⣦⣀ ⢀⡴⠟⠁ ⠙⠷⣄ ⢀⡴⠋ ⠈⠳⣄ ⣠⠟ ⠘⢧⡀ ⢠⠏ ⠈⢧ ⡟ ⠘⡇ ⢸⠃ ⢻ 62%
export function gauge(ui: Container, _theme: Theme): void {
// A semicircular dial. Wants at least 9x5.
ui.gauge({ value: 62, label: "62%" });
}Donut
donutMetersA ring split into labelled, colored segments.
⢀⣤⣴⣶⣿⣿⣿⣿⣿⣿⣷⣶⣤⣄ ⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀ ⢀⣼⣿⣿⣿⣿⣿⣿⠿⠛⠛⠛⠛⠻⢿⣿⣿⣿⣿⣿⣿⣄ ⣼⣿⣿⣿⣿⣿⠏ ⠈⢿⣿⣿⣿⣿⣿⡄ ⢠⣿⣿⣿⣿⣿⠇ ⢿⣿⣿⣿⣿⣧ ⠸⣿⣿⣿⣿⣿⡄ ⣼⣿⣿⣿⣿⡿ ⢿⣿⣿⣿⣿⣷⡄ ⣴⣿⣿⣿⣿⣿⠇ ⠘⢿⣿⣿⣿⣿⣿⣷⣤⣀⣀⣀⣀⣠⣴⣿⣿⣿⣿⣿⣿⠟ ⠈⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋ ⠈⠙⠻⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠛⠉ ⠉⠉⠉⠉⠉⠉⠁
export function donut(ui: Container, theme: Theme): void {
ui.donut({
segments: [
{ value: 4.65, color: theme.primary, label: "Used" },
{ value: 10.96, color: theme.warning, label: "Free" },
],
});
}Inputs
Checkbox
checkboxInputsA checkbox or a toggle, depending on the variant.
[▮ ] Toggle [ ] Checkbox
export function checkbox(ui: Container, _theme: Theme): void {
ui.row({ size: 1, gap: 2 }, (r) => {
r.checkbox({ label: "Toggle", checked: true, variant: "toggle", size: 12 });
r.checkbox({ label: "Checkbox", checked: false, size: 14 });
r.spacer("fill");
});
}Select
selectInputsA closed value, or an open dropdown with the current option marked.
Dracula ▴ Dark Dracula Nord Tokyo Night
export function select(ui: Container, _theme: Theme): void {
ui.select({
value: "Dracula",
width: 20,
size: 20,
open: true,
options: ["Dark", "Dracula", "Nord", "Tokyo Night"],
selectedIndex: 1,
});
}Text input
textInputInputsA labelled field with a placeholder and a cursor.
Search postgres Filter type to filter…
export function textInput(ui: Container, _theme: Theme): void {
ui.textInput({ label: "Search", value: "postgres", size: 1 });
ui.spacer(1);
ui.textInput({ label: "Filter", value: "", placeholder: "type to filter…", size: 1 });
}Tabs
tabsInputsA single row of tabs with one active. Clickable.
1 dashboard 2 traffic 3 sessions 4 network
export function tabs(ui: Container, _theme: Theme): void {
ui.tabs({
tabs: ["1 dashboard", "2 traffic", "3 sessions", "4 network"],
active: 1,
});
}Overlays
Modal
modalOverlaysA centered dialog drawn over everything, with buttons.
╭────────────── Confirm Action ──────────────╮ │ │ │ Terminate process 4821 (postgres)? │ │ │ │ This cannot be undone. │ │ │ │ Yes No │ │ │ ╰────────────────────────────────────────────╯
export function modal(ui: Container, _theme: Theme): void {
// Overlays draw over everything already on the screen, centered.
ui.modal({
title: "Confirm Action",
width: 46,
height: 9,
message: "Terminate process 4821 (postgres)?\n\nThis cannot be undone.",
buttons: [
{ label: "Yes", variant: "success", focused: true },
{ label: "No", variant: "ghost" },
],
});
}Command palette
commandPaletteOverlaysA query line and filtered results, over the current screen.
╭─ Command Palette ────────────────────────────────────────╮ │› the │ │──────────────────────────────────────────────────────────│ │ Toggle theme F2 │ │ Filter processes F3 │ │ Sort by memory F6 │ ╰──────────────────────────────────────────────────────────╯
export function commandPalette(ui: Container, _theme: Theme): void {
ui.commandPalette({
query: "the",
items: [
{ label: "Toggle theme", hint: "F2" },
{ label: "Filter processes", hint: "F3" },
{ label: "Sort by memory", hint: "F6" },
],
selected: 0,
});
}Tooltip
tooltipOverlaysA small floating box anchored at a cell.
Tooltips are overlays positioned at a cell, for hover and hin… ╭──────────────────╮ │swap is 87% full │ ╰──────────────────╯
export function tooltip(ui: Container, _theme: Theme): void {
ui.text("Tooltips are overlays positioned at a cell, for hover and hints.");
ui.tooltip({ text: "swap is 87% full", x: 6, y: 3 });
}Want the longer version? The High Quality Terminal UI Cookbook walks through building a real dashboard with these.