caarlos0/dotfiles/tui-design

Claude Code and agent skill by caarlos0/dotfiles

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Design terminal user interfaces and interactive CLIs that stay usable, accessible, and scriptable. Use when building or reviewing a TUI, a full-screen terminal app, or an interactive command-line tool.

Install

npx vskill@latest install caarlos0/dotfiles/tui-design

SKILL.md

TUI Design

Design the command-line interface first. Add a full-screen TUI only when the task needs persistent context, fast navigation, selection, or live updates. A TUI owns the screen, the input, the scrollback, and the terminal state; do not take that cost without a reason.

Decide if a TUI is correct

  • Use a plain command when the operation is one-shot, batchable, or scripted.
  • Use the user's $PAGER when the task is reading or searching long output.

Page only when stdout is a terminal, and provide --no-pager.

  • Use a TUI when users repeatedly navigate, compare, filter, select, or watch

changing data. Process monitors, resource browsers, and Git interfaces are good examples.

  • Do not build a TUI only to get color or animation. Styled output, a prompt,

or a single selector is usually enough.

  • Never make the TUI the only way to do an important operation. Ship

equivalent non-interactive subcommands and structured output:

``bash app # optional interactive interface app list --json app delete ID --yes ``

  • Share the domain logic below both interfaces. The TUI is an adapter.

Keep the two interfaces separate

  • Write results to stdout and write progress, warnings, and errors to stderr.
  • Test each stream for a terminal independently. stdout can be a pipe while

stderr is still a terminal.

  • When output is not a terminal, change presentation only, never meaning.

Remove color, cursor motion, progress animation, and the alternate screen.

  • Provide --json for structured data and one JSON document per line for

streaming events. Treat those schemas as a public interface.

  • Prompt only when stdin is a terminal. Provide a flag for every prompt so a

script can supply the answer. Never block a pipeline on input.

  • Support -q/--quiet and --verbose, with the same meaning everywhere.

Lay out for a character grid

  • Make 80x24 the minimum target. Essential navigation, current state, primary

actions, and errors must stay usable there.

  • Recompute the layout from the current size on every resize. Handle

SIGWINCH, then query the real dimensions; do not cache the launch size.

  • Use explicit breakpoints. Drop decoration and secondary metadata before you

hide primary content. Below the minimum, print a plain "terminal too small".

  • Clamp sizes before subtracting. Test 1x1, 20x5, and zero-size reports.
  • Give headers, status bars, and key hints fixed heights; give the main view

the rest; set minimum sizes on interactive panes.

  • Wrap prose. Truncate scan-oriented rows with an ellipsis and offer a detail

view. An ellipsis signals hidden content; silent clipping does not.

  • Use the alternate screen for sustained applications, and inline rendering

for command-shaped work that should leave output in scrollback.

  • Spend borders carefully: two bordered panes cost at least four columns at
  1. Prefer whitespace, alignment, and one shared divider.

Handle text as the terminal sees it

  • Measure rendered cells, not bytes, code points, or string length. CJK is

usually two cells, combining marks are zero, and one emoji may be many code points.

  • Cut and edit at grapheme-cluster boundaries, not code points.
  • Do not treat East Asian Width as a complete width algorithm; ambiguous

characters vary by terminal, locale, and font.

  • Keep emoji out of borders, fixed columns, progress tracks, and cursors.

Reported widths disagree across terminals and multiplexers.

  • Treat Nerd Font and Powerline glyphs as optional. Keep an ASCII fallback and

never let an unlabeled icon be the only meaning.

  • Normalize tabs to a fixed width before measuring.

Use color as an enhancement

  • Design a readable monochrome hierarchy first, then add semantic color.
  • Support the ladder: no color, 16 colors, 256 colors, true color. Detect the

capability; do not assume true color, and remember COLORTERM is lost through ssh and sudo.

  • Define roles such as muted, surface, selected, success, warning, and error.

Map roles through themes; do not scatter literal colors in the code.

  • Detect the terminal background before you choose fixed colors, and keep an

unknown state for terminals that do not answer.

  • Never encode meaning in color alone. Add a label, symbol, border, or cursor.
  • Honor NO_COLOR when it is present and not empty. An explicit flag or the

user config may still override it.

  • Target 4.5:1 contrast for ordinary text. You do not control the user's font

size, theme, or rendering.

Bind keys that every terminal can send

  • Support arrows and hjkl. Show arrows in the footer and the vim aliases in

the full help.

  • Make Ctrl+C a global, highest-priority exit that no focused widget can

shadow. It must restore the terminal before it exits.

  • Bind q to quit at the root, ? to full help, / to filter, : to a

command palette, Enter to activate, Tab/Shift+Tab to move focus.

  • Give Esc one consistent ladder: close a menu, cancel an edit or filter,

close the overlay, go back one level. At the root it does nothing.

  • Leave Ctrl+S, Ctrl+Q, Ctrl+D, and Ctrl+Z alone. They belong to the

terminal and the shell.

  • Do not require Ctrl+Shift, Ctrl+Enter, or a Tab and Ctrl+I

distinction. Legacy terminals cannot encode them. Use the Kitty keyboard protocol only for optional accelerators.

  • Keep mouse support off by default unless direct manipulation is central.

Mouse reporting breaks ordinary text selection; document the Shift or Option override, and keep full keyboard equivalence.

Make the interface discoverable

  • Keep a one-line footer with three to six actions for the current focus, for

example up/down move Enter open / filter ? help q quit.

  • Derive the footer from the active keymap so it cannot drift from reality.
  • ? opens a full help overlay grouped by global, navigation, current pane,

actions, and search.

  • List aliases together, and never advertise a key that does nothing. Dim

disabled actions and give the reason.

  • Show the active sort, filter, mode, and match count. Do not overload plain

typing silently.

  • Treat empty states as content. Distinguish "nothing exists", "no match",

"loading", "failed", and "no access", and give the next action: No matches for "prod". Press Esc to clear the filter.

Report state and errors honestly

  • Print something within 100ms. Silence during slow work looks like a hang.
  • Use a spinner only for unknown durations; switch to counts or a progress

measure when totals are known. Always label what is happening.

  • Stop animation on success, failure, cancellation, or non-interactive output,

and keep the frame rate low enough for ssh and slow terminals.

  • Show loading inside the affected pane and keep the surrounding context.
  • Every error answers three questions: what failed, why, and what to do next.

Rewrite low-level errors into domain language. Put the action last.

  • Group repeated failures under one explanation instead of one paragraph each.
  • Keep recoverable errors inside the TUI, preserve the user's input and

selection, and offer the retry key. Restore the terminal before a fatal error prints.

  • Confirm irreversible, broad, or externally visible actions, and name the

target: Delete 12 pods in production? Default the focus to cancel.

  • Do not confirm actions with reliable undo. Offer undo instead, and only

advertise it when recovery is guaranteed.

Structure the application

  • Keep one source of truth for state, and one serialized path for every

visible state transition.

  • Make the update step a deterministic transition: no clock, no filesystem, no

network, no globals inside it.

  • Turn every occurrence into a typed event: keys, resize, ticks, completion,

failure, cancellation.

  • Return descriptions of effects instead of performing them in the update

step. Keep rendering pure and cheap.

  • Keep the event loop free. Anything slower than a few milliseconds is worker

work that sends its result back as an event.

  • Cancel stale work and also reject stale results with a generation counter;

an old response must never overwrite a newer one.

  • Tie worker lifetime to the screen or widget that started it.
  • Keep the domain packages free of the TUI framework, of styling, and of

terminal APIs, so the same code serves --json, tests, and scripts.

  • Reuse existing primitives for lists, tables, viewports, inputs, and help

before writing your own; they already solved Unicode, scrolling, and paste.

  • Never log to the stream the TUI owns. Log to a file and tail -f it from a

second terminal. Debug from a second process.

Own the terminal state

  • Centralize acquisition and restoration. Track raw mode, alternate screen,

hidden cursor, mouse capture, focus reporting, and bracketed paste, and undo them in reverse order.

  • Restore on normal exit, on error, on panic, on SIGINT, and on SIGTERM.

Make cleanup idempotent and best effort.

  • Treat pasted text as data, not as keybindings, and bound its size.
  • Accept that SIGKILL cannot be handled. Document reset or stty sane.
  • Render on state change, not in a free loop. Build the next frame, diff it,

and write only the changed cells. Do not clear the whole screen every frame.

  • Virtualize long lists, key the selection by item identity rather than row

position, and bound streaming queues so producers apply backpressure.

Configure with a clear precedence

  • Order: flags, environment variables, project config, user config, system

config, built-in defaults. Provide a command that shows effective values and their source.

  • Match the mechanism to the lifetime: flags vary per run, environment varies

per machine, project files are shared, user config is personal.

  • Follow XDG: XDG_CONFIG_HOME, XDG_STATE_HOME, XDG_DATA_HOME,

XDG_CACHE_HOME, XDG_RUNTIME_DIR.

  • Ship a complete default keymap and theme. High configurability without a

good baseline is onboarding debt. Make reset easy.

  • Map keys to semantic actions such as item.open, not to functions, and

detect conflicts.

Design for screen readers

  • Assume a screen reader sees a text buffer, not your widgets. Borders and

coordinates carry no meaning to it.

  • Provide a first-class linear mode: --plain, --no-interactive, or an

accessible prompt mode that echoes context and asks one question at a time.

  • Do not assume a framework is accessible because it is popular. Verify with

NVDA, JAWS, and VoiceOver separately; snapshot tests prove nothing here.

  • Keep a real cursor where the user's attention is.
  • There is no portable accessibility environment variable. NO_COLOR is the

only established convention; anything else must be documented and paired with a flag.

Verify it

  • Test state transitions without a terminal: send an event, assert the new

state and the requested effect.

  • Snapshot the rendered buffer at fixed sizes. Cover narrow, wide, empty,

Unicode, selected, error, and loading states.

  • Golden-test the plain contract with redirected streams, TERM=dumb, and

NO_COLOR=1. Assert that no escape sequence, prompt, or timestamp appears.

  • Add pseudo-terminal tests for lifecycle: raw mode, resize, paste, Ctrl+C,

SIGTERM, and panic. After each failure path, assert the terminal was restored.

  • Exercise the real thing through ssh and tmux, and on more than one emulator.
  • Use recorded tapes for documentation, not as the regression gate.

Related skills

  • writing-tests for deterministic tests, including the TUI cases above.
  • my-code-review when reviewing someone else's terminal interface.
  • go-conventions when the implementation is Go.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
222
Repository forks
12
7-day trend score
0

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/caarlos0/dotfiles/tui-design/badge)](https://verified-skill.com/skills/caarlos0/dotfiles/tui-design)

Something wrong with this skill or its page? Tell us.