Awesome-omni-skill ts-tui-game-skill
Build beautiful, performant TUI applications and games with TypeScript and blessed. Provides patterns, templates, and best practices for terminal interfaces.
git clone https://github.com/diegosouzapw/awesome-omni-skill
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/development/ts-tui-game-skill-neversight" ~/.claude/skills/diegosouzapw-awesome-omni-skill-ts-tui-game-skill && rm -rf "$T"
skills/development/ts-tui-game-skill-neversight/SKILL.mdTS 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:
const 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:
src/ 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
directly from event handlers.screen.render()
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:
orqC-c
Performance Rules
Hard rules:
- Never
in tight loopsscreen.render() - Never rebuild widget trees every frame
- Never create new elements every tick without destroying them
Soft rules:
- Update content/styles on existing elements
- One
per frame for large viewportssetContent() - Keep tag parsing minimal in grid renders
- Use
only where necessaryalwaysScroll
Modal Pattern
- Create semi-transparent overlay covering screen
- Create centered modal box on top
- Capture input in modal
- On close:
both, restore focusdestroy()
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
and clears timersscreen.destroy() - Tags used sparingly in large grid renders
- Keyboard-first design (mouse is bonus)