React for CLI - build interactive command-line interfaces using React components and Flexbox layouts
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Comprehensive input handling system for creating interactive CLI applications with keyboard navigation, focus management, and stream access.
These context Props types are available for import and use with the corresponding hooks:
/**
* Props type for AppContext - used with useApp hook
*/
interface AppProps {
/**
* Exit (unmount) the whole Ink app
* @param error - Optional error to exit with
*/
readonly exit: (error?: Error) => void;
}
/**
* Props type for StdinContext - used with useStdin hook
*/
interface StdinProps {
/**
* Stdin stream passed to render() or process.stdin by default
*/
readonly stdin: NodeJS.ReadStream;
/**
* Ink's version of setRawMode that handles Ctrl+C properly
* Use instead of process.stdin.setRawMode
*/
readonly setRawMode: (value: boolean) => void;
/**
* Whether the current stdin supports setRawMode
*/
readonly isRawModeSupported: boolean;
/**
* Internal flag for exit on Ctrl+C behavior
*/
readonly internal_exitOnCtrlC: boolean;
/**
* Internal event emitter for input events
*/
readonly internal_eventEmitter: EventEmitter;
}
/**
* Props type for StdoutContext - used with useStdout hook
*/
interface StdoutProps {
/**
* Stdout stream passed to render() or process.stdout by default
*/
readonly stdout: NodeJS.WriteStream;
/**
* Write string to stdout while preserving Ink's output
* Useful for displaying external information without conflicts
*/
readonly write: (data: string) => void;
}
/**
* Props type for StderrContext - used with useStderr hook
*/
interface StderrProps {
/**
* Stderr stream passed to render() or process.stderr by default
*/
readonly stderr: NodeJS.WriteStream;
/**
* Write string to stderr while preserving Ink's output
* Useful for displaying external information without conflicts
*/
readonly write: (data: string) => void;
}Usage Examples:
import React from "react";
import {
render,
Text,
useApp,
useStdin,
useStdout,
useStderr,
type AppProps,
type StdinProps,
type StdoutProps,
type StderrProps
} from "ink";
// Using Props types for custom contexts or components
function CustomAppProvider({ children }: { children: React.ReactNode }) {
const appContext: AppProps = {
exit: (error?: Error) => {
console.log("Custom exit handler");
process.exit(error ? 1 : 0);
}
};
return (
<AppContext.Provider value={appContext}>
{children}
</AppContext.Provider>
);
}Hook for handling user keyboard input with detailed key information parsing.
/**
* Handle user keyboard input with parsed key information
* @param inputHandler - Function called for each input event
* @param options - Input handling configuration
*/
function useInput(
inputHandler: (input: string, key: Key) => void,
options?: InputOptions
): void;
interface InputOptions {
/**
* Enable or disable input capturing
* @default true
*/
isActive?: boolean;
}
interface Key {
upArrow: boolean; // Up arrow key pressed
downArrow: boolean; // Down arrow key pressed
leftArrow: boolean; // Left arrow key pressed
rightArrow: boolean; // Right arrow key pressed
pageDown: boolean; // Page Down key pressed
pageUp: boolean; // Page Up key pressed
return: boolean; // Return (Enter) key pressed
escape: boolean; // Escape key pressed
ctrl: boolean; // Ctrl key pressed
shift: boolean; // Shift key pressed
tab: boolean; // Tab key pressed
backspace: boolean; // Backspace key pressed
delete: boolean; // Delete key pressed
meta: boolean; // Meta key pressed
}Usage Examples:
import React, { useState } from "react";
import { render, Box, Text, useInput } from "ink";
// Basic input handling
function InputDemo() {
const [input, setInput] = useState("");
const [lastKey, setLastKey] = useState("");
useInput((input, key) => {
if (key.return) {
setInput("");
return;
}
if (key.backspace) {
setInput(prev => prev.slice(0, -1));
return;
}
if (input) {
setInput(prev => prev + input);
}
// Track special keys
if (key.upArrow) setLastKey("Up Arrow");
if (key.downArrow) setLastKey("Down Arrow");
if (key.ctrl) setLastKey("Ctrl pressed");
if (key.escape) setLastKey("Escape");
});
return (
<Box flexDirection="column">
<Text>Input: {input}</Text>
<Text>Last key: {lastKey}</Text>
<Text dimColor>Type text, use arrows, press Enter to clear</Text>
</Box>
);
}
// Navigation with arrow keys
function Navigation() {
const [selectedIndex, setSelectedIndex] = useState(0);
const items = ["Option 1", "Option 2", "Option 3"];
useInput((input, key) => {
if (key.upArrow) {
setSelectedIndex(prev => Math.max(0, prev - 1));
}
if (key.downArrow) {
setSelectedIndex(prev => Math.min(items.length - 1, prev + 1));
}
});
return (
<Box flexDirection="column">
{items.map((item, index) => (
<Text key={index} color={index === selectedIndex ? "blue" : "white"}>
{index === selectedIndex ? "► " : " "}{item}
</Text>
))}
</Box>
);
}
// Conditional input handling
function ConditionalInput() {
const [isActive, setIsActive] = useState(true);
useInput((input, key) => {
if (input === "q") {
setIsActive(false);
}
}, { isActive });
return (
<Text color={isActive ? "green" : "red"}>
Input {isActive ? "active" : "inactive"}. Press 'q' to toggle.
</Text>
);
}Hook for making components focusable and managing focus navigation with Tab key.
/**
* Make component focusable for Tab navigation
* @param options - Focus configuration
* @returns Focus state and control functions
*/
function useFocus(options?: FocusOptions): FocusResult;
interface FocusOptions {
/**
* Enable or disable focus for this component
* @default true
*/
isActive?: boolean;
/**
* Auto focus this component if no active component exists
* @default false
*/
autoFocus?: boolean;
/**
* Assign ID for programmatic focusing
*/
id?: string;
}
interface FocusResult {
/**
* Whether this component is currently focused
*/
isFocused: boolean;
/**
* Focus a specific component by ID
*/
focus: (id: string) => void;
}Usage Examples:
import React, { useState } from "react";
import { render, Box, Text, useInput, useFocus } from "ink";
// Focusable button component
function FocusableButton({ children, onSelect, id }: {
children: string;
onSelect: () => void;
id?: string;
}) {
const { isFocused } = useFocus({ id });
useInput((input, key) => {
if (key.return && isFocused) {
onSelect();
}
});
return (
<Box>
<Text color={isFocused ? "blue" : "white"} inverse={isFocused}>
{isFocused ? "► " : " "}{children}
</Text>
</Box>
);
}
// Menu with focusable items
function FocusableMenu() {
const [selected, setSelected] = useState("");
return (
<Box flexDirection="column">
<Text>Use Tab to navigate, Enter to select:</Text>
<FocusableButton
id="option1"
onSelect={() => setSelected("Option 1")}
>
Option 1
</FocusableButton>
<FocusableButton
id="option2"
onSelect={() => setSelected("Option 2")}
>
Option 2
</FocusableButton>
<FocusableButton
id="option3"
onSelect={() => setSelected("Option 3")}
>
Option 3
</FocusableButton>
{selected && (
<Text color="green">Selected: {selected}</Text>
)}
</Box>
);
}
// Auto-focus example
function AutoFocusInput() {
const { isFocused } = useFocus({ autoFocus: true });
const [value, setValue] = useState("");
useInput((input, key) => {
if (!isFocused) return;
if (key.backspace) {
setValue(prev => prev.slice(0, -1));
} else if (input) {
setValue(prev => prev + input);
}
});
return (
<Box>
<Text>Input: </Text>
<Text color="blue" inverse={isFocused}>{value || " "}</Text>
</Box>
);
}Hook for programmatically controlling focus across all components.
/**
* Control focus management for all components
* @returns Focus management functions
*/
function useFocusManager(): FocusManagerResult;
interface FocusManagerResult {
/**
* Enable focus management for all components
*/
enableFocus: () => void;
/**
* Disable focus management, active component loses focus
*/
disableFocus: () => void;
/**
* Switch focus to next focusable component
*/
focusNext: () => void;
/**
* Switch focus to previous focusable component
*/
focusPrevious: () => void;
/**
* Focus specific component by ID
*/
focus: (id: string) => void;
}Usage Examples:
import React from "react";
import { render, Box, Text, useInput, useFocus, useFocusManager } from "ink";
function FocusController() {
const { enableFocus, disableFocus, focusNext, focusPrevious, focus } = useFocusManager();
useInput((input, key) => {
if (input === "e") enableFocus();
if (input === "d") disableFocus();
if (input === "n") focusNext();
if (input === "p") focusPrevious();
if (input === "1") focus("first");
if (input === "2") focus("second");
});
return (
<Box flexDirection="column">
<Text>Focus Control:</Text>
<Text dimColor>e: enable, d: disable, n: next, p: previous</Text>
<Text dimColor>1: focus first, 2: focus second</Text>
<FocusableItem id="first">First Item</FocusableItem>
<FocusableItem id="second">Second Item</FocusableItem>
</Box>
);
}
function FocusableItem({ id, children }: { id: string; children: string }) {
const { isFocused } = useFocus({ id });
return <Text color={isFocused ? "blue" : "white"}>{children}</Text>;
}Hook for controlling the application lifecycle.
/**
* Access application control functions
* @returns Application control interface
*/
function useApp(): AppResult;
interface AppResult {
/**
* Exit (unmount) the whole Ink app
* @param error - Optional error to exit with
*/
exit: (error?: Error) => void;
}Usage Examples:
import React, { useEffect } from "react";
import { render, Text, useInput, useApp } from "ink";
// Exit on specific key
function ExitableApp() {
const { exit } = useApp();
useInput((input, key) => {
if (input === "q" || key.escape) {
exit();
}
});
return <Text>Press 'q' or Escape to exit</Text>;
}
// Exit with error
function ErrorApp() {
const { exit } = useApp();
useEffect(() => {
const timer = setTimeout(() => {
exit(new Error("Something went wrong"));
}, 3000);
return () => clearTimeout(timer);
}, [exit]);
return <Text color="red">App will exit with error in 3 seconds...</Text>;
}Hooks for accessing stdin, stdout, and stderr streams directly.
/**
* Access stdin stream and related utilities
*/
function useStdin(): StdinResult;
/**
* Access stdout stream and write function
*/
function useStdout(): StdoutResult;
/**
* Access stderr stream and write function
*/
function useStderr(): StderrResult;
interface StdinResult {
/**
* Input stream
*/
stdin: NodeJS.ReadStream;
/**
* Set raw mode for stdin (use Ink's version, not process.stdin.setRawMode)
*/
setRawMode: (value: boolean) => void;
/**
* Whether stdin supports raw mode
*/
isRawModeSupported: boolean;
/**
* Internal flag for exit on Ctrl+C behavior
*/
internal_exitOnCtrlC: boolean;
/**
* Internal event emitter for input events
*/
internal_eventEmitter: EventEmitter;
}
interface StdoutResult {
/**
* Output stream where app renders
*/
stdout: NodeJS.WriteStream;
/**
* Write string to stdout while preserving Ink's output
* Useful for displaying external information without conflicts
*/
write: (data: string) => void;
}
interface StderrResult {
/**
* Error stream for error output
*/
stderr: NodeJS.WriteStream;
/**
* Write string to stderr while preserving Ink's output
* Useful for displaying external information without conflicts
*/
write: (data: string) => void;
}Usage Examples:
import React, { useEffect, useState } from "react";
import { render, Text, useStdin, useStdout } from "ink";
// Direct stream access
function StreamAccess() {
const { stdin, setRawMode, isRawModeSupported } = useStdin();
const stdout = useStdout();
const [rawMode, setRawModeState] = useState(false);
useEffect(() => {
if (isRawModeSupported) {
setRawMode(true);
setRawModeState(true);
return () => {
setRawMode(false);
setRawModeState(false);
};
}
}, [setRawMode, isRawModeSupported]);
return (
<Text>
Raw mode: {rawMode ? "enabled" : "disabled"}
(supported: {isRawModeSupported ? "yes" : "no"})
</Text>
);
}