Repo of the Day
vadimdemedes/ink
Published: Feb 13, 2023
Open repository ↗🌈 React for interactive command-line apps
Summary
Ink is a React renderer for command-line interfaces, written in TypeScript and published under the MIT license. It lets developers build CLI output using the same component model as browser React, with Yoga handling Flexbox layout. The README lists many users, including Cloudflare's Wrangler, Prisma, Shopify CLI, and Claude Code.
What it is useful for
Ink fits when a CLI needs more than plain console.log output: multi-step interactive prompts, progress displays, tables, or anything that benefits from component composition. Because every element behaves like a flex container, layout is expressed with familiar CSS-like properties (flexDirection, justifyContent, gap, padding) on a <Box> component, while text styling (color, bold, italic, underline, backgroundColor) lives on <Text>. Hooks such as useInput, useFocus, useWindowSize, and useApp cover keyboard handling, focus management, terminal resize, and graceful exit. The README also documents hooks for stdin/stdout, paste handling, cursor control, and animation, plus a <Static> component for rendering log lines above live output. Color values are passed through chalk under the hood, so any chalk-supported color string works.
How engineers can use it
Install Ink alongside React:
npm install ink react
For a fresh project, the README points to a scaffolder:
npx create-ink-app my-ink-cli
# or, for TypeScript:
npx create-ink-app --typescript my-ink-cli
A counter example from the docs uses useState and useEffect together with render(<Counter />). For manual setup, the README walks through installing @babel/preset-react and transpiling JSX with Babel, or using a loader like import-jsx to transpile on the fly. The process keeps the Node event loop alive only while there is pending work (timers, listeners, pending promises), so a render with no async work exits immediately. To stop the app, you can press Ctrl+C, call exit() from useApp, or call unmount() on the object returned by render(). Use waitUntilExit() to run code after unmount.
Two documented limitations to keep in mind: all text must live inside a <Text> (a <Box> cannot be placed inside <Text>), and minWidth/maxWidth do not support percentages because of an upstream Yoga issue.