React for CLI - build interactive command-line interfaces using React components and Flexbox layouts
npx @tessl/cli install tessl/npm-ink@5.2.0Ink is a React-like framework for building command-line interfaces that brings the familiar component-based development experience of React to terminal applications. It provides a comprehensive set of UI components powered by Facebook's Yoga layout engine to enable Flexbox-style layouts in the terminal, making it possible to create interactive CLI tools with real-time UI updates, focus management, and sophisticated terminal-based interfaces.
npm install ink reactimport { render, Box, Text, useInput, useApp } from "ink";For type imports:
import {
render,
Box,
Text,
useInput,
useApp,
type AppProps,
type StdinProps,
type StdoutProps,
type StderrProps
} from "ink";For CommonJS:
const { render, Box, Text, useInput, useApp } = require("ink");import React, { useState } from "react";
import { render, Box, Text, useInput, useApp } from "ink";
function Counter() {
const [count, setCount] = useState(0);
const { exit } = useApp();
useInput((input, key) => {
if (input === "q") {
exit();
}
if (key.upArrow) {
setCount(count + 1);
}
if (key.downArrow) {
setCount(count - 1);
}
});
return (
<Box>
<Text color="green">Counter: {count}</Text>
<Text dimColor> (Use ↑/↓ to change, 'q' to quit)</Text>
</Box>
);
}
// Render the app
render(<Counter />);Ink is built around several key components:
Core functionality for mounting and rendering React component trees to the terminal with full lifecycle management.
function render(
node: ReactNode,
options?: NodeJS.WriteStream | RenderOptions
): Instance;
interface RenderOptions {
stdout?: NodeJS.WriteStream; // default: process.stdout
stdin?: NodeJS.ReadStream; // default: process.stdin
stderr?: NodeJS.WriteStream; // default: process.stderr
debug?: boolean; // default: false
exitOnCtrlC?: boolean; // default: true
patchConsole?: boolean; // default: true
}
interface Instance {
rerender: (node: ReactNode) => void;
unmount: () => void;
waitUntilExit: () => Promise<void>;
cleanup: () => void;
clear: () => void;
}Essential components for building terminal layouts using Flexbox-style positioning and styling.
// Primary layout container component
function Box(props: BoxProps & { children?: ReactNode }): JSX.Element;
// Text display component with styling options
function Text(props: TextProps & { children?: ReactNode }): JSX.Element;
interface BoxProps {
// Flexbox layout properties
flexDirection?: "row" | "column" | "row-reverse" | "column-reverse";
flexWrap?: "nowrap" | "wrap" | "wrap-reverse";
flexGrow?: number;
flexShrink?: number;
justifyContent?: "flex-start" | "flex-end" | "center" | "space-between" | "space-around" | "space-evenly";
alignItems?: "flex-start" | "center" | "flex-end" | "stretch";
// [Additional layout properties...]
}
interface TextProps {
color?: string;
backgroundColor?: string;
bold?: boolean;
italic?: boolean;
underline?: boolean;
strikethrough?: boolean;
wrap?: "wrap" | "truncate-end" | "truncate-start" | "truncate-middle";
}Comprehensive input handling system for creating interactive CLI applications with keyboard navigation and focus management.
// Handle keyboard input with parsed key information
function useInput(
inputHandler: (input: string, key: Key) => void,
options?: { isActive?: boolean }
): void;
// Focus management for component navigation
function useFocus(options?: {
isActive?: boolean;
autoFocus?: boolean;
id?: string;
}): { isFocused: boolean; focus: (id: string) => void };
interface Key {
upArrow: boolean;
downArrow: boolean;
leftArrow: boolean;
rightArrow: boolean;
return: boolean;
escape: boolean;
ctrl: boolean;
shift: boolean;
tab: boolean;
backspace: boolean;
delete: boolean;
meta: boolean;
}// DOM element reference type for measuring and manipulation
interface DOMElement {
nodeName: string;
attributes: Record<string, any>;
childNodes: DOMNode[];
yogaNode?: YogaNode;
parentNode?: DOMElement;
style: Styles;
}
// Context Props types for hooks (also available as individual exports)
interface AppProps {
readonly exit: (error?: Error) => void;
}
interface StdinProps {
readonly stdin: NodeJS.ReadStream;
readonly setRawMode: (value: boolean) => void;
readonly isRawModeSupported: boolean;
readonly internal_exitOnCtrlC: boolean;
readonly internal_eventEmitter: EventEmitter;
}
interface StdoutProps {
readonly stdout: NodeJS.WriteStream;
readonly write: (data: string) => void;
}
interface StderrProps {
readonly stderr: NodeJS.WriteStream;
readonly write: (data: string) => void;
}
// Comprehensive styling interface based on Yoga layout
interface Styles {
// Layout
position?: "absolute" | "relative";
display?: "flex" | "none";
overflow?: "visible" | "hidden";
// Flexbox
flexDirection?: "row" | "column" | "row-reverse" | "column-reverse";
flexWrap?: "nowrap" | "wrap" | "wrap-reverse";
flexGrow?: number;
flexShrink?: number;
flexBasis?: number | string;
justifyContent?: "flex-start" | "flex-end" | "center" | "space-between" | "space-around" | "space-evenly";
alignItems?: "flex-start" | "center" | "flex-end" | "stretch";
alignSelf?: "flex-start" | "center" | "flex-end" | "auto";
// Dimensions
width?: number | string;
height?: number | string;
minWidth?: number | string;
minHeight?: number | string;
// Spacing
margin?: number;
marginX?: number;
marginY?: number;
marginTop?: number;
marginBottom?: number;
marginLeft?: number;
marginRight?: number;
padding?: number;
paddingX?: number;
paddingY?: number;
paddingTop?: number;
paddingBottom?: number;
paddingLeft?: number;
paddingRight?: number;
gap?: number;
columnGap?: number;
rowGap?: number;
// Borders
borderStyle?: string;
borderTop?: boolean;
borderBottom?: boolean;
borderLeft?: boolean;
borderRight?: boolean;
borderColor?: string;
borderTopColor?: string;
borderBottomColor?: string;
borderLeftColor?: string;
borderRightColor?: string;
// Text
textWrap?: "wrap" | "truncate-end" | "truncate-start" | "truncate-middle" | "end" | "middle";
}