CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-ink

React for CLI - build interactive command-line interfaces using React components and Flexbox layouts

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

hooks.mddocs/

User Input Handling

Comprehensive input handling system for creating interactive CLI applications with keyboard navigation, focus management, and stream access.

Types

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>
  );
}

Capabilities

Input Handling

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>
  );
}

Focus Management

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>
  );
}

Focus Manager

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>;
}

Application Control

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>;
}

Stream Access

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>
  );
}

docs

components.md

hooks.md

index.md

rendering.md

tile.json