building-the-ink-tui

Builds and extends Ink/React terminal menus with htm, theme tokens, and live progress panels.

2|1|Updated Jun 29, 2026
One-click install
npx skills add https://github.com/frederico-kluser/newsletter-crawler --skill building-the-ink-tui-frederico-kluser
Or copy as Structured Prompt for Agent
Please help me install this Agent Skill.
Skill: building-the-ink-tui
Source: https://github.com/frederico-kluser/newsletter-crawler/tree/main/.agents/skills/building-the-ink-tui
Command: npx skills add https://github.com/frederico-kluser/newsletter-crawler --skill building-the-ink-tui-frederico-kluser

SYSTEM DOCUMENTATION & REQUIREMENTS

💡 This Skill requires ink, react, @inkjs/ui, htm.

What problem does it solve? Building a guided terminal UI in Node.js without a build step is error-prone: htm has no JSX fragment shorthand, @inkjs/ui widgets default to clashing colors, uncontrolled TextInput buffers bleed between wizard steps, and un-unref'd animation timers hang the test runner. This Skill encodes the verified patterns and Ink gotchas for the newsletter-crawler TUI so new screens, menus, and progress panels work the first time. ## Core Features & Use Cases - No-build component authoring: Write Ink components with htm (html\<${Box}>``) instead of JSX, with the correct Fragment workaround and pinned ink@5/react@18/@inkjs/ui@2 stack. - Semantic theme layer: Centralize colors, glyphs, and @inkjs/ui overrides via theme.js tokens and ThemeProvider, with shared Panel/FooterHints/Header widgets. - Interaction patterns: Checkbox multi-select with parallel Esc-only useInput, navigable lists with in-place preview via a single branching useInput, and label-driven menu navigation in tests. - Live progress dashboards: Route logs through setLogSink into a bounded ring buffer, poll status/telemetry, and render a crawl dashboard with phase ProgressBars separated from a curated event feed. - Use Case: When adding a new screen to the guided menu (e.g., a sources manager with async re-detect actions), follow the SourcesView pattern: pure component, props-injected side effects, single useInput branching by mode, and spy-based tests. ## Quick Start Ask the assistant to add a new screen to the guided terminal menu following the building-the-ink-tui patterns, then verify with npm test and a real TTY run.

Frequently Asked Questions about building-the-ink-tui

High-intent search queries and answers about installing and using this skill.

FAQPage Schema
How do I build an Ink terminal UI without a JSX build step?

Use htm bound to React's createElement and author components as tagged template literals like html`<${Box}>`. The stack is ink@5, react@18, @inkjs/ui@2, and htm@3, all running directly under Node ESM with no transpilation.

How do I test Ink CLI components with ink-testing-library?

Render the component with ink-testing-library and drive stdin, navigating menus by label rather than counting arrow-down presses so reordering does not break tests. Never assert on color since components mounted outside ThemeProvider fall back to the default theme; assert text and glyphs instead.

Why does htm fail with InvalidCharacterError on JSX fragments?

htm has no naked fragment syntax, so <>...</> parses as a tag named '' and React throws InvalidCharacterError, rendering a blank screen. Use the explicit Fragment component with the closing form <//> instead.

Why does my TextInput show the previous field's text in an Ink wizard?

@inkjs/ui TextInput is uncontrolled and seeds its buffer from defaultValue only at mount, so React reuses the fiber when consecutive steps render the same component type at the same position. Give each TextInput a unique key per step to force a remount with a fresh buffer.

Why does node --test hang after my Ink tests finish?

A setInterval used for spinner animation or polling that is not unref'd keeps the event loop alive after tests complete, hanging the process-isolated test runner. Call .unref() on every animation and polling timer; Ink itself keeps the loop alive while rendering.

How do I show live command progress in an Ink UI?

Route logs through a setLogSink callback into a bounded ring buffer (around 200 lines, not Ink's Static which grows unbounded) and poll status getters every ~300ms. Unset the sink in both the promise finally block and the effect cleanup to avoid leaking state between runs.