From tui-design
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 vs Ratatui vs Textual vs Ink"), design questions ("how should I lay out this dashboard"), and concrete build requests ("build me a TUI for X"), even when the user doesn't say "TUI" explicitly — phrases like "terminal app", "ncurses-style", "interactive shell tool", "CLI dashboard", "fzf-like picker", or naming a known TUI app (lazygit, k9s, btop, helix, yazi) all qualify.
How this skill is triggered — by the user, by Claude, or both
Slash command
/tui-design:tui-designThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
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/`.
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/.
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 |
If the user hasn't named a language, ask which ecosystem before diving into framework specifics. The universal principles below apply regardless.
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:
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.hjkl, /, ?, Esc, q, gg, G) are the lingua franca even for non-vim users — supporting them is a courtesy that costs nothing.Most successful TUIs use one of these. Choose by workflow shape, not by aesthetics:
1–5) jump directly. Used by lazygit, btop, htop. Best for at-a-glance observation and switching between views of related state.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.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.[/]. 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.
Since you can't change font size, hierarchy comes from:
├─ └─ for trees; consistent indent units (2 cells is standard).▶ expandable, ▼ expanded, ● active, ○ inactive, • static bullet.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).
Design in three tiers:
NO_COLOR=1? If layout, weight, and reverse video carry the meaning, yes.$COLORTERM=truecolor.Always respect NO_COLOR (no-color.org). ripgrep, bat, eza, delta, fd all do.
Conventional meanings have crystallized:
Define semantic tokens (status.error, git.staged, text.muted) and theme them. Lipgloss's AdaptiveColor, 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.
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:
Density choices:
Don't decorate. Borders that exist purely for "looks polished" usually make the app feel busier without adding meaning.
Always:
/usr/local/share/...) for paths in lists. Middle truncation (/usr/.../file.txt) when the basename matters. Reserve a cell for the ellipsis.123/45678 like fzf does) when filtering.▲/▼) on the active column.DataTable, Ratatui Table+TableState, Bubbles list, Ink with <Static>.The convention that has converged across nearly every modern TUI:
?.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.
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 |
1–9 | 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:
? opens a help screen with all bindingsSpace- menu is the gold standard)Ctrl+P) — every action with a binding should also be a palette commandModal 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.
These four are the difference between an app that feels professional and one that doesn't:
color_eyre integration, Bubble Tea's defer p.RestoreTerminal(), Textual's exception cleanup, Ink's unmount() all do this.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.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:
tea.LogToFile, ~/.cache/myapp/log), use a separate console (Textual's textual console), or render an in-app log pane (lazygit, k9s).wcwidth lies). Use unicode-segmentation (Rust), golang.org/x/text + mattn/go-runewidth (Go), wcwidth (Python), string-width (JS — Ink uses this) — never len() or .length.Truecolor is now safe to assume in 2026. Detect via $COLORTERM=truecolor; fall back to 256 then 16 then monochrome. The Kitty keyboard protocol (CSI u) is supported by kitty, foot, WezTerm, Alacritty, iTerm2, Ghostty, Rio, and Windows Terminal — opt-in for advanced bindings (Ctrl+I distinct from Tab, Shift+Enter distinct from Enter), always with legacy fallback.
SSH and tmux strip features unless explicitly enabled. For tmux:
set -ga terminal-overrides ",*:Tc" # truecolor passthrough
set -g extended-keys on # CSI u
set -g extended-keys-format csi-u
set -g allow-passthrough on # kitty graphics
set -g mouse on # mouse forwarding
set -g set-clipboard on # OSC 52 clipboard
Image protocols are fragmented: kitty graphics (best quality) → Sixel (broadest compat) → iTerm2 inline. yazi auto-detects and supports all three.
TUIs are inherently inaccessible to screen readers. NVDA, JAWS, VoiceOver, and Orca read the visible buffer like a textbox, with no concept of widgets or focus. Best current practices when accessibility matters:
[ERROR], [OK], [!]).--no-tui plain mode that just prints output linearly.textual serve → HTML is currently the best a11y route — same code runs in a browser, where real accessibility tooling exists.If a11y matters seriously, ship a web alternative or a plain-CLI mode alongside the TUI. Don't pretend the TUI alone is accessible.
Most production TUIs support themes via TOML/YAML config (lazygit, bottom, btop, helix, delta, bat, fzf), TCSS files (Textual), or composable styles (Lipgloss). Light/dark detection via OSC ]11;? query or $COLORFGBG; Lipgloss's AdaptiveColor and Textual's runtime theme switching are the cleanest implementations.
Community palettes you should be able to support: Catppuccin (Latte/Frappé/Macchiato/Mocha), Dracula, Nord, Gruvbox, Tokyo Night, Rose Pine, Solarized, base16. Build your theme via semantic tokens, then map tokens → palette colors. Adding a new theme should be one config file, not a code change.
Refer users to these by name when you spot them:
--exact; --preview pane; Tab for multi-select. Used by fzf, skim, telescope.nvim, atuin, zoxide, helix, Textual command palette.1-5 jumps, Tab cycles, single letters trigger panel-specific actions, context-sensitive footer. Trade-off: cognitive load — c does different things in each panel.:pods, :nodes, :svc) with tab-completion and aliases. Fast for power users; demands tab-completion or aliases listing for discovery.Space opens which-key popup.h ascend, l descend. ranger, lf, nnn, yazi, broot.Ctrl+P modal with fuzzy-matched action list. Every action that has a binding should also be a palette command; show keybinding next to command name.For deeper coverage of any of these patterns and the specific apps that exemplify them, read references/exemplar-apps.md.
Ranked by real-world complaint frequency:
SIGWINCH; debounce; never assume fixed dimensions.? → palette.When the user asks you to build something:
Is this a full-screen interactive app, or a one-shot command?
references/cli-basics.md. Apply argparse + color + maybe a spinner and you're done.What ecosystem?
What's the workflow shape? Match to one of the seven canonical layouts above before writing any code. Sketch the panels in ASCII first.
What are the 5–8 most common actions? Those become the always-visible footer hints. Everything else lives behind ? or the command palette.
What's the data model? Lists, trees, tables, forms, free-text? This determines which widgets you need and whether to virtualize.
What's the minimum viable terminal size? Usually 80×24. Decide what gets hidden, collapsed, or stacked at narrower widths.
Then, with the ecosystem reference loaded, write the code. The non-negotiables (alt screen, terminal restoration, resize, suspend, async I/O, no UI-thread blocking) apply regardless of language.
Walk through this checklist:
NO_COLOR honored?? show full help?q and Esc consistent?Most existing TUIs fail 3–5 of these. Calling them out specifically gives the user a concrete improvement path.
When the user asks "should I do X or Y?" — give a recommendation. The terminal renaissance has produced enough convergent design that many questions have a clear best answer (use the alternate screen, support hjkl+arrows, honor NO_COLOR, use semantic color tokens). Don't hedge on settled questions. Hedge on real tradeoffs (modal vs modeless, mouse support, single-key destructive actions vs always-confirm).
When showing code, prefer the idiom of the chosen ecosystem — don't translate Bubble Tea's MVU into Ratatui's immediate-mode and call it good. Each ecosystem has converged on a style; meet it where it is. The reference files document each one in detail.
When the user is stuck on a design decision, point at an exemplar app that solved the same problem (references/exemplar-apps.md) — concrete examples beat abstract principles for design questions.
Guides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.
npx claudepluginhub gfargo/tui-design-skill --plugin tui-design