Skip to main content

pi-tui-design

Create distinctive, crafted TUI components for pi using @mariozechner/pi-tui and @mariozechner/pi-coding-agent. Use when building interactive terminal UIs โ€” custom components, overlays, dialogs, dashboards, widgets, data visualizations, animated elements, game-like interfaces, or any visual TUI work inside pi extensions or custom tools. Triggers on: 'build a TUI component', 'make a dashboard', 'create an overlay', 'interactive widget', 'terminal UI', 'custom component', 'pi-tui', or any request to create visual, interactive terminal interfaces. Also use when beautifying or redesigning existing TUI components.

Source facts

Repository
joelhooks/pi-tools
Last source activity
February 25, 2026 at 19:40
Detected SKILL.md language
English
Stars
84
Forks
1

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
2 files

Showing SKILL.md

SKILL.md
Source instructions ยท Read-only preview
name
pi-tui-design
description
Create distinctive, crafted TUI components for pi using @mariozechner/pi-tui and @mariozechner/pi-coding-agent. Use when building interactive terminal UIs โ€” custom components, overlays, dialogs, dashboards, widgets, data visualizations, animated elements, game-like interfaces, or any visual TUI work inside pi extensions or custom tools. Triggers on: 'build a TUI component', 'make a dashboard', 'create an overlay', 'interactive widget', 'terminal UI', 'custom component', 'pi-tui', or any request to create visual, interactive terminal interfaces. Also use when beautifying or redesigning existing TUI components.
# TUI Design for Pi Build terminal interfaces that feel *crafted*, not generated. The terminal is constrained โ€” fixed-width character grid, keyboard only, theme-dependent colors โ€” and those constraints are a design feature. ## Design Thinking Before coding, commit to a direction: - **Tone**: Minimal and precise? Dense and information-rich? Playful? Industrial? The terminal has its own aesthetic vocabulary โ€” box-drawing elegance, braille-pattern density, block-element weight, symbol clarity. - **Scope**: Full-screen takeover (`ctx.ui.custom`)? Floating overlay? Persistent widget? Status line? Tool rendering? Match the delivery surface to the interaction weight. - **Differentiation**: What detail makes this feel intentional? A progress bar with braille resolution. Aligned columns with accent headers. A dialog with breathing room. ## Terminal Aesthetic Vocabulary ### Unicode Repertoire โ€” Your Typography Terminal UIs have no font choices. Instead, the *character repertoire* is the typography: | Category | Characters | Use | |----------|-----------|-----| | Box-drawing (light) | `โ”€โ”‚โ”Œโ”โ””โ”˜โ”œโ”คโ”ฌโ”ดโ”ผ` | Standard borders, tables | | Box-drawing (rounded) | `โ•ญโ•ฎโ•ฐโ•ฏ` | Softer, modern feel | | Box-drawing (heavy) | `โ”โ”ƒโ”โ”“โ”—โ”›โ”ฃโ”ซโ”ณโ”ปโ•‹` | Emphasis, headers | | Box-drawing (double) | `โ•โ•‘โ•”โ•—โ•šโ•โ• โ•ฃโ•ฆโ•ฉโ•ฌ` | Formal, structured | | Block elements | `โ–ˆโ–“โ–’โ–‘โ–€โ–„โ–Œโ–` | Progress bars, density, fill | | Braille | `โ €โ โ ‚โ ƒ...โฃฟ` | High-resolution patterns, sparklines, charts | | Symbols | `โ—†โ—โ—‹โ—‰โ—Žโœ“โœ—โ–ธโ–นโ–ถโ–ทโ–ณโ–ฝโ˜…โ˜†โ™ฆ` | Status indicators, bullets, selections | | Math/arrows | `โ†’โ†โ†‘โ†“โ†”โ†•โ‡’โŸถโŸตโˆ™โ‹ฏ` | Navigation hints, flow | | Powerline | `โ–‘` | Segment separators (terminal-dependent) | **Hierarchy through character weight**: `โ–ˆ` (heavy) โ†’ `โ–“` (medium) โ†’ `โ–’` (light) โ†’ `โ–‘` (subtle) โ†’ ` ` (empty). Use this for visual density gradients, not just fill. **Aspect ratio**: Terminal cells are ~2:1 (twice as tall as wide). A `โ–ˆโ–ˆ` (two block chars) reads as roughly square. Account for this in any spatial layout โ€” the snake.ts example uses `cellWidth = 2` for this reason. ### Color Discipline **Always use pi's theme tokens.** Hardcoded ANSI escapes break when users switch themes. ```typescript // โœ— Hardcoded โ€” breaks on theme change const red = (s: string) => `\x1b[31m${s}\x1b[0m`; // โœ“ Theme-aware โ€” adapts to dark/light, custom themes const header = theme.fg("accent", theme.bold("Title")); const status = theme.fg("success", "โœ“ OK"); const muted = theme.fg("muted", "secondary info"); ``` **Color hierarchy** (from the 51-token theme system): - `accent` โ€” primary attention, selections, active elements - `text` โ€” default body content (usually `""` = terminal default) - `muted` โ€” secondary, de-emphasized - `dim` โ€” tertiary, barely visible - `success` / `error` / `warning` โ€” semantic status - `border` / `borderAccent` / `borderMuted` โ€” structural elements - `toolTitle` โ€” headers in tool-like contexts **Background colors** for regions: `selectedBg`, `userMessageBg`, `customMessageBg`, `toolPendingBg`, `toolSuccessBg`, `toolErrorBg`. **Rule**: One accent color dominates. Use `muted`/`dim` for everything secondary. Overusing color flattens the hierarchy โ€” a wall of green is worse than no color at all. ### Spatial Composition The `width` parameter is your canvas edge. Every line from `render()` must not exceed it. **Padding rhythm**: Consistent horizontal padding creates visual breathing room. `paddingX=1` (one space each side) is the baseline. Headers may deserve `paddingX=2`. Cramped UIs feel hostile. **Alignment**: Right-align numbers, left-align labels. Use `visibleWidth()` to calculate ANSI-aware column widths. Pad with spaces, not tabs. **Negative space**: An empty `Spacer(1)` between sections does more than a separator line. Let content breathe. **Box nesting**: `Container > Box > [children]` gives you padding + background + vertical stacking. Don't flatten everything into one render function โ€” compose components. ### Motion and Animation No CSS transitions. Timer-based updates via `setInterval` + `tui.requestRender()`. ```typescript // Spinner pattern (see Loader component) const frames = ["โ ‹", "โ ™", "โ น", "โ ธ", "โ ผ", "โ ด", "โ ฆ", "โ ง", "โ ‡", "โ "]; this.interval = setInterval(() => { this.frame = (this.frame + 1) % frames.length; this.invalidate(); tui.requestRender(); }, 80); ``` **Patterns**: Braille spinners, block-element progress bars, staggered list reveals (render items one by one with delay), typewriter text, pulsing indicators. **Cleanup is mandatory**: Clear intervals in `dispose()`. Leaked timers cause rendering after component removal. ### Keyboard Interaction Design Keyboard is the only input. Make it discoverable and consistent. **Standard conventions** (users expect these): - `โ†‘โ†“` or `j/k` โ€” navigate lists - `Enter` โ€” confirm/select - `Escape` โ€” cancel/back - `Tab` โ€” next field - `/` or start typing โ€” search/filter **Always show hints**: Footer line with available keys. Use `keyHint()` for theme-aware formatting. ```typescript const help = theme.fg("dim", "โ†‘โ†“ navigate โ€ข enter select โ€ข esc cancel"); ``` **Use `matchesKey()` from pi-tui** โ€” handles terminal escape sequence differences: ```typescript import { matchesKey, Key } from "@mariozechner/pi-tui"; handleInput(data: string) { if (matchesKey(data, Key.up)) { /* ... */ } else if (matchesKey(data, Key.enter)) { /* ... */ } else if (matchesKey(data, Key.escape)) { /* ... */ } else if (matchesKey(data, Key.ctrl("c"))) { /* ... */ } } ``` ## The Component Contract Every pi-tui component implements: ```typescript interface Component { render(width: number): string[]; // Lines of output, each โ‰ค width handleInput?(data: string): void; // Keyboard input when focused wantsKeyRelease?: boolean; // Kitty protocol key release events invalidate(): void; // Clear cached render state } ``` **Critical rules**: 1. Each line from `render()` must not exceed `width` โ€” use `truncateToWidth()` 2. Call `invalidate()` when state changes, then `tui.requestRender()` to trigger re-render 3. Cache rendered output (`cachedLines`/`cachedWidth`) โ€” re-compute only when state or width changes 4. Reapply styles per line โ€” ANSI resets at line boundaries ### Caching Pattern ```typescript private cachedWidth?: number; private cachedLines?: string[]; private version = 0; private cachedVersion = -1; render(width: number): string[] { if (this.cachedLines && this.cachedWidth === width && this.cachedVersion === this.version) { return this.cachedLines; } // ... compute lines ... this.cachedWidth = width; this.cachedLines = lines; this.cachedVersion = this.version; return lines; } invalidate(): void { this.cachedWidth = undefined; this.cachedLines = undefined; } ``` Increment `this.version` on state changes. Theme changes call `invalidate()` automatically. ### Theme Invalidation If you pre-bake theme colors into child components, rebuild them on `invalidate()`: ```typescript override invalidate(): void { super.invalidate(); // Clear child render caches this.rebuildContent(); // Re-apply current theme colors } ``` ## Available Components ### From `@mariozechner/pi-tui` | Component | Purpose | Key API | |-----------|---------|---------| | `Text` | Multi-line word-wrapped text | `new Text(content, paddingX, paddingY, bgFn?)`, `.setText()` | | `TruncatedText` | Single-line truncated text | `new TruncatedText(text, paddingX, paddingY)` | | `Box` | Padded container with background | `new Box(paddingX, paddingY, bgFn)`, `.addChild()`, `.setBgFn()` | | `Container` | Vertical stack of children | `.addChild()`, `.removeChild()`, `.clear()` | | `Spacer` | Empty vertical space | `new Spacer(lines)` | | `Markdown` | Rendered markdown with syntax highlighting | `new Markdown(content, paddingX, paddingY, mdTheme)` | | `Image` | Terminal image (Kitty/iTerm2/Ghostty/WezTerm) | `new Image(base64, mimeType, theme, options)` | | `SelectList` | Interactive list with filter/scroll | items, maxVisible, theme; `.onSelect`, `.onCancel` | | `SettingsList` | Toggle settings with values | items, maxVisible, theme, onChange, onClose | | `Loader` | Braille spinner with message | `new Loader(tui, spinnerColor, messageColor, message)` | | `CancellableLoader` | Loader with escape-to-cancel | wraps Loader with abort signal | | `Input` | Single-line text input | implements `Focusable` for IME cursor positioning | | `Editor` | Multi-line text editor | full editing with undo, kill-ring, keybindings | ### From `@mariozechner/pi-coding-agent` | Component | Purpose | Key API | |-----------|---------|---------| | `DynamicBorder` | Width-adaptive horizontal border | `new DynamicBorder((s: string) => theme.fg("accent", s))` | | `BorderedLoader` | Loader with borders + cancel | `new BorderedLoader(tui, theme, message)`, `.signal`, `.onAbort` | | `CustomEditor` | Editor with app keybindings baked in | Extend for modal editing (vim), custom shortcuts | ### Utilities ```typescript import { visibleWidth, truncateToWidth, wrapTextWithAnsi } from "@mariozechner/pi-tui"; import { matchesKey, Key } from "@mariozechner/pi-tui"; import { DynamicBorder, getMarkdownTheme, keyHint } from "@mariozechner/pi-coding-agent"; ``` - `visibleWidth(str)` โ€” display width ignoring ANSI escape codes - `truncateToWidth(str, width, ellipsis?)` โ€” truncate with optional ellipsis - `wrapTextWithAnsi(str, width)` โ€” word wrap preserving ANSI codes - `matchesKey(data, key)` โ€” compare keyboard input against key identifiers - `getMarkdownTheme()` โ€” theme object for `Markdown` component - `keyHint(action, description)` โ€” theme-aware keybinding hint text ## Delivery Surfaces Choose the right surface for the interaction: ### Full-screen takeover โ€” `ctx.ui.custom(component)` For complex interactions: dashboards, games, multi-step wizards. ```typescript await ctx.ui.custom((tui, theme, keybindings, done) => { return new MyComponent(tui, theme, () => done(result)); }); ``` ### Overlay โ€” `ctx.ui.custom(factory, { overlay: true })` Floats on top of existing content. For quick selections, confirmations, panels. ```typescript const result = await ctx.ui.custom<string | null>( (tui, theme, kb, done) => new MyDialog(theme, done), { overlay: true, overlayOptions: { anchor: "center", // 9 positions: center, top-left, top-center, etc. width: "50%", // number or percentage string minWidth: 40, maxHeight: "80%", offsetX: -2, offsetY: 0, margin: 2, // or { top, right, bottom, left } visible: (w, h) => w >= 80, // responsive hide }, } ); ``` ### Widget โ€” `ctx.ui.setWidget(id, lines|factory, options?)` Persistent display above or below the editor. For status, progress, lists. ```typescript ctx.ui.setWidget("my-widget", (tui, theme) => ({ render: () => [theme.fg("accent", "โ— Active") + " โ€” 3 items pending"], invalidate: () => {}, })); // placement: "belowEditor" for below // Clear: ctx.ui.setWidget("my-widget", undefined) ``` ### Status line โ€” `ctx.ui.setStatus(id, content)` Single-line persistent indicator in footer. ```typescript ctx.ui.setStatus("mode", theme.fg("accent", "โ— DESIGN")); // Clear: ctx.ui.setStatus("mode", undefined) ``` ### Tool rendering โ€” `renderCall`/`renderResult` Custom display for tool calls in the conversation. Return `Text` components with `(0, 0)` padding โ€” the wrapping `Box` handles padding. ```typescript renderCall(args, theme) { return new Text(theme.fg("toolTitle", theme.bold("my_tool ")) + theme.fg("muted", args.action), 0, 0); }, renderResult(result, { expanded, isPartial }, theme) {
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub