--- name: ink-cli description: Build terminal CLI apps using Ink (React for CLIs). This skill should be used when the user wants to create command-line interfaces with React components, terminal UIs, interactive CLI tools, or needs help with Ink components, hooks, and patterns. --- # Ink CLI Development Ink is a React renderer for building command-line interfaces. It uses Yoga for Flexbox layouts and provides the same component-based experience as React in the browser. ## Quick Start ```bash # Scaffold new project npx create-ink-app my-cli # With TypeScript (recommended) npx create-ink-app --typescript my-cli ``` ## Core Concepts ### Every Element is a Flexbox Container Think of `` as `
`. All text must be wrapped in ``. ### Component Hierarchy ```tsx import {render, Box, Text} from 'ink'; const App = () => ( Title Left Right ); render(); ``` ## Essential Components ### `` - Styled Text ```tsx Green text Hex color Styled Dimmed Inverted Long text will be truncated... ``` ### `` - Flexbox Container ```tsx // Layout // Spacing // Dimensions {/* Percentage of parent */} // Borders // Background ``` **Border styles:** `single`, `double`, `round`, `bold`, `singleDouble`, `doubleSingle`, `classic` ### `` - Permanent Output Renders above everything else, never re-renders. Use for logs, completed items. ```tsx const App = () => { const [logs, setLogs] = useState([]); return ( <> {(log, i) => {log}} Live UI here ); }; ``` ### `` and `` ```tsx Line 1Line 2 Left {/* Pushes Right to the edge */} Right ``` ### `` - String Transformation ```tsx output.toUpperCase()}> hello {/* Renders: HELLO */} ``` ## Essential Hooks ### `useInput` - Keyboard Input ```tsx import {useInput, useApp} from 'ink'; const App = () => { const {exit} = useApp(); useInput((input, key) => { if (input === 'q') exit(); if (key.leftArrow) { /* handle left */ } if (key.return) { /* handle enter */ } if (key.escape) { /* handle escape */ } }); return Press q to quit; }; ``` **Key properties:** `leftArrow`, `rightArrow`, `upArrow`, `downArrow`, `return`, `escape`, `ctrl`, `shift`, `tab`, `backspace`, `delete`, `pageUp`, `pageDown`, `meta` ### `useApp` - App Control ```tsx const {exit} = useApp(); exit(); // Clean exit exit(error); // Exit with error ``` ### `useFocus` - Focus Management ```tsx const Item = ({label}) => { const {isFocused} = useFocus(); return ( {isFocused ? '>' : ' '} {label} ); }; ``` **Options:** `autoFocus`, `isActive`, `id` ### `useFocusManager` - Programmatic Focus ```tsx const {focusNext, focusPrevious, focus} = useFocusManager(); focus('specific-id'); ``` ### `useStdout` / `useStderr` - Direct Output ```tsx const {write} = useStdout(); write('Direct to stdout\n'); // Bypasses Ink rendering ``` ## Common Patterns ### Loading Spinner ```tsx import Spinner from 'ink-spinner'; const Loading = ({text}) => ( {' '}{text} ); ``` ### Progress Indicator ```tsx const Progress = ({percent}) => { const width = 20; const filled = Math.round(width * percent / 100); return ( {'█'.repeat(filled)} {'░'.repeat(width - filled)} {percent}% ); }; ``` ### Selectable List ```tsx const List = ({items}) => { const [selected, setSelected] = useState(0); useInput((input, key) => { if (key.upArrow) setSelected(s => Math.max(0, s - 1)); if (key.downArrow) setSelected(s => Math.min(items.length - 1, s + 1)); }); return ( {items.map((item, i) => ( {i === selected ? '>' : ' '} {item} ))} ); }; ``` ### Table Layout ```tsx const Table = ({data}) => ( {data.map((row, i) => ( {row.map((cell, j) => ( {cell} ))} ))} ); ``` ## Testing ```tsx import {render} from 'ink-testing-library'; const {lastFrame, rerender} = render(); expect(lastFrame()).toContain('expected text'); ``` ## API Reference For complete API documentation, see `references/ink-api.md`. For community components (spinners, inputs, tables, etc.), see `references/components.md`. ## Tips 1. **All text in ``**: Never put raw text directly in `` 2. **Use `` for logs**: Prevents re-rendering of completed output 3. **Disable input when needed**: `useInput(handler, {isActive: false})` 4. **Debug with React DevTools**: Run with `DEV=true my-cli` 5. **Handle Ctrl+C**: Enabled by default, disable with `exitOnCtrlC: false` in render options