TS TUI Game Skill
Build beautiful, performant TUI applications and games with TypeScript and blessed.
Triggers
Use this skill when:
- •Creating a new TUI application or game
- •Building terminal-based interfaces with blessed
- •Implementing game loops, render schedulers, or input handling in terminals
- •Designing layouts for terminal applications
- •Working with blessed widgets (Box, List, Form, etc.)
- •Optimizing terminal rendering performance
- •Adding keyboard and mouse input handling
- •Creating modals, panels, or complex UI compositions
Keywords: tui, terminal, blessed, ncurses, console, cli game, terminal game, terminal ui, text interface
Core Instructions
When helping with blessed TUI development, follow these principles:
Project Setup
- •
Always use TypeScript with strict mode
- •
Recommended screen options:
typescriptconst screen = blessed.screen({ smartCSR: true, // Efficient change-scroll-region rendering autoPadding: true, // Automatic border/padding handling fullUnicode: true, // Support for box drawing and unicode warnings: true, // Development warnings }); - •
Directory structure - Separate game logic from UI:
codesrc/ main.ts # Entrypoint, screen init, lifecycle ui/ app.ts # Root composition, layout creation theme.ts # Centralized theme tokens widgets/ # Custom widgets panels/ # Composed windows input/ keymap.ts # Key binding definitions input-router.ts # Focus-aware dispatch game/ state.ts # Authoritative state types reducer.ts # Pure state updates systems/ # Simulation systems engine/ scheduler.ts # Tick + render scheduling events.ts # Event bus
Rendering Rules
CRITICAL: Never call screen.render() directly from event handlers.
Use a render scheduler pattern:
export function createRenderScheduler(screen: blessed.Widgets.Screen, maxFps = 30) {
let pending = false;
const frameMs = Math.floor(1000 / maxFps);
const timer = setInterval(() => {
if (!pending) return;
pending = false;
screen.render();
}, frameMs);
return {
requestRender() { pending = true; },
stop() { clearInterval(timer); }
};
}
State Management
- •Single authoritative state - One state object owned by game core
- •Pure reducers - Actions in, state out, no side effects
- •UI dispatches actions - Never mutate state directly from widgets
Layout Guidelines
Standard 3-pane game layout:
- •Main viewport: map/scene (largest, fluid)
- •Right sidebar: stats, inventory (24-32 cols fixed)
- •Bottom log: messages, hints (7-12 rows fixed)
const sidebarWidth = 30;
const logHeight = 9;
const main = blessed.box({
parent: screen,
top: 0, left: 0,
width: `100%-${sidebarWidth}`,
height: `100%-${logHeight}`,
border: 'line',
});
Theme System
Centralize all colors and styles:
export const theme = {
fg: 'white',
bg: 'black',
panel: { fg: 'white', bg: 'black', border: { fg: '#888888' } },
accent: { fg: 'black', bg: '#f4d03f' },
danger: { fg: 'white', bg: 'red' },
muted: { fg: '#aaaaaa', bg: 'black' },
};
Rule: Pick 1 accent color and 1 danger color. Everything else muted.
Input Handling
Implement a router pattern for context-aware input:
function onKey(ch: string, key: blessed.Widgets.Events.IKeyEventArg) {
const name = key.full || ch;
if (globalKeys[name]) return globalKeys[name]();
if (state.ui.modal) return modalHandler(ch, key);
if (state.ui.activePanel === 'map') return mapHandler(ch, key);
}
screen.on('keypress', onKey);
Standard controls:
- •Movement: arrows + hjkl
- •Help:
? - •Cycle focus:
tab - •Confirm:
enter - •Back/close:
esc - •Quit:
qorC-c
Performance Rules
Hard rules:
- •Never
screen.render()in tight loops - •Never rebuild widget trees every frame
- •Never create new elements every tick without destroying them
Soft rules:
- •Update content/styles on existing elements
- •One
setContent()per frame for large viewports - •Keep tag parsing minimal in grid renders
- •Use
alwaysScrollonly where necessary
Modal Pattern
- •Create semi-transparent overlay covering screen
- •Create centered modal box on top
- •Capture input in modal
- •On close:
destroy()both, restore focus
Cleanup (REQUIRED)
function safeExit(screen: blessed.Widgets.Screen, code = 0) {
try {
screen.destroy();
} finally {
process.exit(code);
}
}
screen.key(['escape', 'q', 'C-c'], () => safeExit(screen));
Resources
See the resources/ directory for:
- •Style Guide - Comprehensive development guidelines
- •Widget Reference - Blessed widget API quick reference
See the templates/ directory for starter code.
Best Practices Checklist
When reviewing or creating blessed TUI code, verify:
- • One render scheduler controls
screen.render() - • Game core is pure and testable (no terminal dependency)
- • Layout uses stable proportions and resize behavior
- • Theme tokens are centralized
- • Controls are discoverable (
?help, footer hints) - • Modals capture input and restore focus
- • No per-tick widget creation
- • Exit path calls
screen.destroy()and clears timers - • Tags used sparingly in large grid renders
- • Keyboard-first design (mouse is bonus)