AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Tui Design

skill-gfargo-tui-design-skill-tui-design · by gfargo

Design and build clean, professional, minimal terminal UI (TUI) applications and command-line tools. Use this skill whenever the user is building, designing, refactoring, reviewing, or asking about terminal interfaces — full-screen TUIs (file managers, dashboards, monitors, git/k8s tools, REPLs), interactive CLI prompts, or simple command-line utilities. Use it for library questions ("Bubble Tea…

No reviews yet
0 installs
24 views
0.0% view→install

Install

$ agentstack add skill-gfargo-tui-design-skill-tui-design

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution Used
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-gfargo-tui-design-skill-tui-design)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
1mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Tui Design? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

TUI & CLI Design

Build terminal applications that feel professional — the way lazygit, k9s, btop, helix, fzf, and yazi feel. The terminal is enjoying a renaissance: Charm (Go), Ratatui (Rust), Textual (Python), and Ink (TypeScript) have each crystallized a mature philosophy. This skill teaches the universal patterns that make TUIs feel good plus per-ecosystem deep-dives in references/.

When to read which reference

Use this skill's body for the universal principles below. Then load reference files on demand:

| Situation | Read | |---|---| | User picked Go / mentioned Bubble Tea, Charm, Lipgloss, tview, gocui | references/ecosystem-go.md | | User picked Rust / mentioned Ratatui, crossterm, tui-rs, Cursive | references/ecosystem-rust.md | | User picked Python / mentioned Textual, Rich, prompt_toolkit, urwid | references/ecosystem-python.md | | User picked TS/JS / mentioned Ink, blessed, OpenTUI, Clack, Inquirer | references/ecosystem-typescript.md | | Building a non-interactive CLI (no full-screen UI) | references/cli-basics.md | | Designing layout, borders, color, typography, density | references/visual-patterns.md | | Designing keybindings, focus, navigation, modal vs modeless | references/interaction-patterns.md | | Studying what makes specific apps great (lazygit, k9s, fzf, btop, helix, yazi, atuin) | references/exemplar-apps.md | | Testing or debugging a TUI | that ecosystem's references/ecosystem-*.md (Testing / Debugging sections) | | Inline vs full-screen; clipboard, hyperlinks, notifications (OSC) | references/visual-patterns.mdInline, alt-screen, or overlay; references/interaction-patterns.mdTalking to the terminal emulator |

If the user hasn't named a language, ask which ecosystem before diving into framework specifics. The universal principles below apply regardless.


The terminal is a constrained design medium

Every cell is the same width. Type size doesn't change. You have ~80×24 characters at the small end, maybe 200×60 if you're lucky. You can't draw arbitrary pixels; you compose grids of characters with foreground/background colors and a handful of attributes (bold, dim, italic, underline, reverse). These constraints are the point — they force clarity. When something feels cramped or noisy in a TUI, the answer is almost never "add more"; it's usually "remove something or use whitespace."

Three observations that drive everything else:

  1. Spatial memory is the navigation. Users learn where things live: the file list is left, the diff is right, the status bar is bottom. Once that's established, panels must never move without explicit action. Reordering panels on focus is among the worst sins a TUI can commit.
  2. Color encodes meaning, not appearance. Treat colors as semantic tokens (status.error, text.muted, accent.primary), not raw hex codes. The app should be usable in monochrome — color is enhancement, never the only signal. ~8% of males have red-green CVD; pair color with letters or symbols.
  3. Keyboard is primary; mouse is augmentation. Every action must be reachable from the keyboard. Mouse can speed things up but never gates functionality. Vim motions (hjkl, /, ?, Esc, q, gg, G) are the lingua franca even for non-vim users — supporting them is a courtesy that costs nothing.

The seven canonical layouts

Most successful TUIs use one of these. Choose by workflow shape, not by aesthetics:

  • Persistent multi-panel — All panels visible in fixed positions, focus shifts between them. Numeric keys (15) jump directly. Used by lazygit, btop, htop. Best for at-a-glance observation and switching between views of related state.
  • Miller columns — Three (or N) columns: parent → current → preview. h/l ascend/descend. Used by yazi, ranger, broot. Best for hierarchies (filesystems, JSON, K8s resources). Degrades poorly on narrow terminals — provide a single-pane fallback.
  • Drill-down stack — Browser-style: navigate deeper with a back-stack, Esc returns. Often paired with command-mode navigation (:pods, :nodes). Used by k9s, lazydocker. Best when there are many resource types and the user needs to pivot between them.
  • Widget dashboard — Independent widgets in a grid, each owning its data lifecycle. Layout configurable via TOML/YAML. Used by bottom, btop, glances. Best for monitoring/observability where users want to compose their own view.
  • IDE three-panel — Sidebar → main content → detail/output, often with tabs in the main panel. Used by Posting, Harlequin, helix. Best for editor-like workflows.
  • Overlay / popup — Appears over the shell, does one thing, exits. Used by fzf, atuin, zoxide+fzf. Best for "summon → choose → output" interactions. Render either full-screen on the alternate screen or inline with a bounded height (fzf's --height); either way, clean up on exit and print the result to stdout. See references/visual-patterns.mdInline, alt-screen, or overlay.
  • Tabbed within panel — Tab bars cycled with [/]. Used inside larger layouts (lazygit's Local/Remotes/Tags, lazydocker's Logs/Stats/Env tabs). Best when one panel needs multiple personalities without changing the global layout.

The universal rule: panels never move without explicit user action.

Visual hierarchy without varying type size

Since you can't change font size, hierarchy comes from:

  • Position — top/left reads first; status bar at bottom; headers at top.
  • Color and weight — bold + accent color for titles and focused panel borders; dim for metadata, timestamps, disabled items; default weight for primary text.
  • Reverse video — universally available since VT100; the canonical way to mark current selection. Works on every terminal.
  • Indentation and connectors├─ └─ for trees; consistent indent units (2 cells is standard).
  • Whitespace and bullets expandable, expanded, active, inactive, static bullet.
  • Borders for focus — border color change is the strongest focus indicator. Lipgloss, Ratatui, Textual, and Ink all support per-side border styling.

Use bold for titles, selection labels, and primary content. Use dim for metadata and disabled items. Use italic sparingly (poorly supported on many terminals — never the only signal). Use underline for hyperlinks (OSC 8) and shortcut hints. Use reverse video for the cursor row and current selection. Avoid blink (disabled in most modern terminals; accessibility hazard) and strikethrough (limited support).

Color as a semantic system

Design in three tiers:

  1. Monochrome — does the app work with NO_COLOR=1? If layout, weight, and reverse video carry the meaning, yes.
  2. 16 ANSI — does it look right with the user's theme (Solarized, Gruvbox, whatever)? You don't control these; theme-coherent palettes do.
  3. 256 / truecolor — fine-grained palette for designed themes (Catppuccin, Dracula, Nord). Detect via $COLORTERM=truecolor.

Always respect NO_COLOR (no-color.org). ripgrep, bat, eza, delta, fd all do.

Conventional meanings have crystallized:

  • Green → success, added, online
  • Red → error, deleted, danger
  • Yellow → warning, modified, pending
  • Cyan / Blue → info, paths, links
  • Magenta → special, highlights
  • Dim / gray → secondary, disabled

Define semantic tokens (status.error, git.staged, text.muted) and theme them. Lipgloss's LightDark (v2; AdaptiveColor in v1/compat), Textual's CSS variables, and Ratatui's palette pipelines all implement this indirection. Scattering hex codes through code is a phase you grow out of.

Never use color alone. Pair with letters (lazygit's file status: M modified, A added, D deleted, ?? untracked) or symbols (delta's +/- line prefixes). Safe color pairs for CVD: blue+orange, blue+yellow, black+white.

Borders, density, and whitespace

Use single-line borders (─ │ ┌ ┐ └ ┘) by default. Rounded (╭ ╮ ╰ ╯) is the modern Charm aesthetic — fine, slightly softer. Heavy (━ ┃ ┏) for emphasis sparingly. Avoid double-line (═ ║ ╔) — it reads as "DOS." Always provide ASCII fallback (+, -, |) for legacy SSH and TERM=dumb.

When to use borders vs whitespace:

  • Borders — when the pane has dynamic content needing a visible boundary, when focus state must be shown, when adjacent panels need clear separation.
  • Whitespace alone — when content is static (htop has no internal borders) or density matters more than structure. A single blank row often beats a heavy border.

Density choices:

  • Pack when data is scanned at a glance, updates in real time, or is read horizontally across rows (htop, btop, k9s).
  • Pad when reading prose, filling forms, or making single decisions (gum/huh forms, Glow markdown, Posting).

Don't decorate. Borders that exist purely for "looks polished" usually make the app feel busier without adding meaning.

Two reflexes to apply unprompted

These are the two things the default instinct misses most, because users rarely ask for them by name — and a strong base model will answer the literal question without raising either. Apply both to any layout you design or review, even when the user asked about something else entirely (a color choice, a keybinding, "why does this feel busy"). This is where most of the value is.

1. Run a clutter audit — make "feels busy" countable. Never answer "it feels noisy" with "simplify it." Count the offenders and name the specific cuts: border-nesting depth (more than one border between the terminal edge and the content is too many; an outer full-screen frame is almost always redundant), how many separate signals encode the same state ([PASS] + green + + a row marker is four), markers that sit on every row (a glyph on 100% of rows marks nothing), and the ratio of cells spent on chrome — borders, labels, repeated boilerplate like a full datestamp on every log line — versus actual data. The full method is in references/visual-patterns.mdThe clutter audit.

2. Pressure-test the floor. A layout designed at the author's own window size is unfinished — they never see it break because they only ever see their own terminal. State concretely what happens at 80×24 and a 60-column tmux split: what collapses to a single pane, what hides, what truncates, and the "terminal too small" message below the minimum. Multi-column layouts (Miller columns, 2×N grids) must have a single-pane fallback. Raise this in every layout review even when size was never mentioned — it is the single most-missed issue in TUI design, and "it looks great on my screen" is exactly the blind spot it addresses. Breakpoint ladder in references/visual-patterns.mdResponsive design.

Tables and lists

Always:

  • Align numerics right, text left, dates as fixed-width ISO-8601.
  • Truncate, don't wrap, in cells. Tail truncation (/usr/local/share/...) for paths in lists. Middle truncation (/usr/.../file.txt) when the basename matters. Reserve a cell for the ellipsis.
  • Show a count (123/45678 like fzf does) when filtering.
  • Sort indicator (/) on the active column.
  • Detail-on-Enter as the universal escape hatch — pressing Enter on a row reveals all fields in a side panel or modal. This lets you hide low-priority columns at narrow widths without losing access to the data.
  • Virtualize any list that might exceed a few hundred items. k9s renders thousands of pods, Toolong tails multi-GB logs — both virtualize. Built into Textual DataTable, Ratatui Table+TableState, Bubbles list, Ink with ``.

Status bars, headers, footers

The convention that has converged across nearly every modern TUI:

  • Header (top) — persistent context: what app, what dataset, what mode. htop's CPU/mem meters; k9s's cluster/context/namespace; lazygit's branch and repo.
  • Main area (middle) — the panels. This is where the work happens.
  • Status / mode line — ephemeral feedback ("Saved", "3 files changed") with auto-fade. Vim-style mode indicators (NORMAL/INSERT/SELECT) with distinct cursor shapes.
  • Footer hint bar (bottom) — 3–5 most-useful shortcuts always visible, full reference behind ?.

The footer hint bar is the single most important discoverability tool. htop's F1–F10 strip; lazygit's per-pane hints; Bubble Tea's bubbles/help auto-generates from the keymap; Textual's Footer widget renders bindings declared via BINDINGS. Don't make users read docs to discover basic actions.

Keys: discoverability and conventions

Cross-app conventions that have crystallized — use these unless you have a strong reason not to:

| Key | Action | |---|---| | q | quit | | ? | help | | / | search | | n / N | next / prev match | | Esc | cancel / back | | Enter | confirm / drill in | | Space | toggle / mark for multi-select | | : | command mode | | gg / G | top / bottom | | Tab / Shift+Tab | switch focus | | r | refresh | | 19 | jump to panel / numbered tab | | hjkl and arrows | move (support both) |

Never bind these — they belong to the terminal:

  • Ctrl+C (SIGINT — should always quit cleanly)
  • Ctrl+Z (SIGTSTP — suspend; you must restore terminal state on resume)
  • Ctrl+\ (SIGQUIT)
  • Ctrl+S / Ctrl+Q (XON/XOFF flow control on legacy terminals)

Discoverability is layered:

  1. Always-visible footer hints (3–5 most useful keys)
  2. ? opens a help screen with all bindings
  3. Leader-key prefixes show a which-key popup (helix's Space- menu is the gold standard)
  4. Command palette (Ctrl+P) — every action with a binding should also be a palette command
  5. Documentation as the last resort, not the first

Modal vs modeless is a real choice. Modal apps (vim, helix, k9s ex-mode) get denser keybindings and need persistent mode indicators (status-bar color or label) plus distinct cursor shapes. Modeless apps (Textual, Bubble Tea, btop) lean on widget focus. Both are valid; pick one paradigm and stick with it.

Mouse support is contested. The pragmatic answer: support mouse where it's natural (clicking a tab, scrolling a list, focusing a pane) but require nothing of it. Every mouse-reachable target needs a keyboard equivalent. Note that mouse capture disables terminal text-selection — most emulators bypass with Shift.

The non-negotiables (terminal hygiene)

These four are the difference between an app that feels professional and one that doesn't:

  1. Use the alternate screen for full-screen TUIs. Don't pollute the user's scrollback. On exit, the terminal returns to where it was.
  2. Always restore terminal state on exit — even on panic. Install panic/atexit handlers that disable raw mode, leave alt screen, and restore the cursor before printing the trace. A panicking TUI that leaves raw mode + alt screen is the worst possible UX. Ratatui's color_eyre integration, Bubble Tea's defer p.RestoreTerminal(), Textual's exception cleanup, Ink's unmount() all do this.
  3. Handle resize (SIGWINCH). Re-layout on every resize event; debounce rapid resizes. Define a minimum size (typically 80×24) and render a clear "terminal too small" message rather than crash. Use percentages, fr units, min/max, and ratios — never absolute positions.
  4. Handle suspend (Ctrl+Z / SIGTSTP). On suspend: disable raw mode, leave alt screen, restore cursor, then kill(0, SIGTSTP). On SIGCONT: re-enter alt screen and force a full redraw. Windows lacks SIGTSTP; that's fine.

Other essentials:

  • Never block the UI thread on I/O. All network/disk/subprocess work happens in goroutines/tasks/promises; results flow back via messages/channels/events.
  • Don't redraw on a fixed timer. Redraw on events. Most apps idle at 0 fps until something happens. Cap animations at 30–60 fps.
  • Logging can't go to stdout. Alt-screen + raw mode wou

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.