nm-cli-screen-system
v1.0.0
Published
Action-based terminal UI system built on Ink/React with auto-grouped footers, dynamic captions, and reusable components
Maintainers
Readme
nm-cli-screen-system
A declarative, action-based terminal UI system built on Ink/React.
Features
✨ Action-Based - Separate what keys do from which keys to listen for
🎯 Auto-Grouping - Footer automatically groups keys: ↑↓←→ to navigate
📦 Reusable Components - Self-contained list, grid, and preview components
🎨 Dynamic Captions - Footer updates based on state (e.g., "PLAYING")
⌨️ Smart Navigation - Left arrow on first item goes back
🔄 Position Memory - Remember scroll position when navigating back
📐 Responsive - Adapts to terminal width automatically
Quick Start
Installation
npm install nm-cli-screen-systemPeer Dependencies: ink ^4.0.0, react ^18.0.0
Simple Menu
import { showListScreen } from 'nm-cli-screen-system';
const choice = await showListScreen({
title: 'Main Menu',
items: [
{ name: 'Start Game', value: 'start' },
{ name: 'Settings', value: 'settings' },
{ name: 'Exit', value: 'exit' }
],
onSelect: (value) => value
});Multi-Column Grid
import { showMultiColumnListScreen } from 'nm-cli-screen-system';
const word = await showMultiColumnListScreen({
title: 'Select a Word',
items: ['apple', 'banana', 'cherry', 'date', ...],
onSelect: (word) => word
});Grid with Live Preview
import { showMultiColumnListWithPreviewScreen } from 'nm-cli-screen-system';
const word = await showMultiColumnListWithPreviewScreen({
title: 'Words',
items: ['nefarious', 'ephemeral', 'ubiquitous'],
getPreviewContent: (word) => `Definition: ...`,
onSelect: (word) => word
});Screen Layout
┌─────────────────────────────────────────────┐
│ ← Menu ← Section │ Title (breadcrumb)
├─────────────────────────────────────────────┤
│ │
│ Body Content │
│ (Lists, grids, custom components) │
│ │
├─────────────────────────────────────────────┤
│ ↑↓ to navigate, enter to select, esc to go back │ Auto-generated
│ Total: 50 items │ Custom footer
└─────────────────────────────────────────────┘Core Concepts
Actions
Define what happens when keys are pressed:
ctx.setAction('playAudio', async () => {
await playSound();
});Key Bindings
Define which keys trigger which actions:
ctx.setKeyBinding({
key: 'p',
caption: 'play sound',
action: 'playAudio'
});Auto-Generated Footer
Keys with the same caption are grouped:
// These four bindings...
{ key: 'upArrow', caption: 'navigate', action: 'moveUp' }
{ key: 'downArrow', caption: 'navigate', action: 'moveDown' }
{ key: 'leftArrow', caption: 'navigate', action: 'moveLeft' }
{ key: 'rightArrow', caption: 'navigate', action: 'moveRight' }
// ...become one footer entry:
"↑↓←→ to navigate"Available Screens
showScreen(config)
Base screen with full control over content and bindings.
showListScreen(config)
Single-column menu with up/down navigation.
showMultiColumnListScreen(config)
Multi-column grid with 4-way arrow navigation.
showMultiColumnListWithPreviewScreen(config)
Grid layout with live preview panel below that updates as you navigate.
Available Components
ListComponent
Vertical list with built-in navigation.
MultiColumnListComponent
Multi-column grid with 4-way navigation and smart left-arrow (goes back when at first item).
MultiColumnListWithPreviewComponent
Grid plus preview panel that shows details of the selected item.
Examples
See examples/basic-spike.js for a complete demo featuring:
- Main menu navigation
- Info screen
- Word grid with 50 words
- Word grid with live definition preview
- Word detail cards with audio playback
- Dynamic footer states
Run it:
node packages/screenSystem/examples/basic-spike.jsDocumentation
- GUIDE.md - Comprehensive developer guide
- DESIGN.md - Design principles and patterns
- FOOTER-SYSTEM.md - Footer composition details
Context API
Methods available in onRender(ctx):
| Method | Description |
|--------|-------------|
| setAction(name, fn) | Define an action handler |
| setKeyBinding(binding) | Add/update key binding(s) |
| updateKeyBinding(key, updates) | Update existing binding |
| addFooter(item) | Add custom footer text |
| setFooter(items) | Replace all custom footer items |
| clearFooter() | Clear custom footer |
| update() | Trigger screen re-render |
| goBack() | Trigger back action |
| close(result) | Close screen with result |
Key Features
Dynamic Captions
Captions can be functions that return strings or styled components:
ctx.setKeyBinding({
key: 'p',
caption: () => {
if (isPlaying) {
return h(Text, { color: 'black', backgroundColor: 'green' }, ' PLAYING ');
}
return 'play sound';
},
action: 'playAudio'
});Result:
- Before:
p to play sound - During:
PLAYING(black on green background)
Position Memory
Screens remember their scroll position:
let savedIndex = 0;
while (true) {
const item = await showMultiColumnListScreen({
items: myItems,
initialSelectedIndex: savedIndex,
onSelect: (item, index) => {
savedIndex = index; // Remember position
return item;
}
});
if (!item) break;
await showDetail(item);
// Returns to same position in list
}Smart Left Arrow
In multi-column lists, pressing left arrow when on the first item goes back to the parent screen - natural and intuitive UX.
Development
# Install dependencies
npm install
# Run demo
node examples/basic-spike.js
# Generate sample data
node examples/generate-sample-words.js
# Download audio pronunciations
node examples/download-sounds-gtts.jsLicense
MIT
