npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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

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-system

Peer 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.js

Documentation

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.js

License

MIT