Ink

repository·master·Indexed 12 days ago

https://github.com/vadimdemedes/ink

A React renderer for command-line interfaces (CLI) that allows developers to build interactive terminal applications using a component-based model and Flexbox layouts via the Yoga engine. Version 7.1.1.

Tokens
17.6K
Snippets
54
Records
65
Agent score
99%

What's inside Ink

  1. Manage component focus with useFocus() and useFocusManager()

    master

    Ink provides a focus system for interactive terminal interfaces.

    useFocus(options?): Makes a component focusable (accessible via <kbd>Tab</kbd>).

    • isFocused (boolean): Indicates if the component is currently focused.
    • options:
      • autoFocus (boolean, default: false): Focuses this component if no other component is focused.
      • isActive (boolean, default: true): Enables/disables focusability.
      • id (string): A unique ID used to programmatically focus this component via useFocusManager.focus(id).

    useFocusManager(): Provides methods to control focus globally.

    • enableFocus(): Enables focus management (enabled by default).
    • disableFocus(): Disables focus management.
    • focusNext(): Moves focus to the next component (triggered by <kbd>Tab</kbd>).
    • focusPrevious(): Moves focus to the previous component (triggered by <kbd>Shift</kbd>+<kbd>Tab</kbd>).
    • focus(id): Moves focus to the component with the specified id.
    • activeId (string | undefined): The ID of the currently focused component.
    import {render, useFocus, Text} from 'ink';
    
    const Example = () => {
    	const {isFocused} = useFocus();
    
    	return <Text>{isFocused ? 'I am focused' : 'I am not focused'}</Text>;
    };
    
    render(<Example />);
  2. Layout with Flexbox and Yoga

    master

    Ink uses the Yoga layout engine, meaning every element behaves like a Flexbox container (similar to a <div> with display: flex in a browser). You can use CSS-like properties to build layouts.

    Important Rules:

    • All text must be wrapped in a <Text> component.
    • Use the <Box> component to create containers and manage layouts using Flexbox properties.
  3. Understand the Ink App Lifecycle

    master

    An Ink app is a Node.js process. It remains active as long as there is work in the event loop (e.g., active timers, pending promises, or useInput listening on stdin). If no async work is present, the app will render once and exit immediately.

    Exiting the App

    To exit the application, you can:

    • Press Ctrl+C (enabled by default via exitOnCtrlC).
    • Call exit() from the useApp hook inside a component.
    • Call unmount() on the object returned by render().

    Running code after exit

    You can use waitUntilExit() to execute logic after the component tree has been unmounted.

    const {waitUntilExit} = render(<MyApp />);
    
    await waitUntilExit();
    
    console.log('App exited');
  4. Enable Screen Reader support

    master

    Ink provides basic ARIA-like support for screen readers. You can enable it in two ways:

    1. Pass {isScreenReaderEnabled: true} as an option to the render function.
    2. Set the INK_SCREEN_READER environment variable to true.

    When enabled, Ink generates screen-reader-friendly output based on ARIA roles and states provided to <Box> and <Text> components.

    render(<MyApp />, {isScreenReaderEnabled: true});
  5. Scaffold a new Ink app with create-ink-app

    master

    The fastest way to start a new project is using the create-ink-app CLI tool. You can scaffold a standard JavaScript project or a TypeScript project.

    # Standard JavaScript scaffold
    npx create-ink-app my-ink-cli
    
    # TypeScript scaffold
    npx create-ink-app --typescript my-ink-cli
  6. Manual JavaScript setup with Babel

    master

    Since Ink uses JSX, you need a transpilation step. You can use Babel with @babel/preset-react to transpile your source files.

    1. Install the preset: npm install --save-dev @babel/preset-react
    2. Configure babel.config.json with {"presets": ["@babel/preset-react"]}.
    3. Transpile your file (e.g., source.js) using Babel: npx babel source.js -o cli.js.
    4. Run the resulting file with Node: node cli.js.

    Alternatively, you can use import-jsx or @esbuild-kit/esm-loader to handle JSX on the fly without a manual build step.

    {
    	"presets": ["@babel/preset-react"]
    }
  7. Use React Devtools with Ink

    master

    Ink supports React Devtools. To use them:

    1. Install react-devtools-core as a dependency.
    2. Run your application with the DEV=true environment variable:
      DEV=true my-cli
    3. Start the Devtools UI in a separate terminal:
      npx react-devtools
      This allows you to inspect the component tree and live-edit props to see immediate changes in your CLI. Note that you must manually exit the CLI using Ctrl+C.
    DEV=true my-cli
  8. How background color propagates in `<Box>`

    master
    When you provide a backgroundColor prop to a <Box>, it automatically provides that color to its children via a backgroundContext. This allows child components to be aware of the parent's background color, which is useful for ensuring text contrast and proper styling in nested layouts.
  9. How `suspendTerminal` and `TerminalSuspension` work

    master

    Ink provides a mechanism to prevent visual corruption when external CLI tools are invoked.

    • SuspendTerminal: A function type that can either accept a callback or return a TerminalSuspension object. It is the primary interface for managing terminal ownership transitions.
    • TerminalSuspension: A handle returned when suspendTerminal() is called without a callback. It provides a resume() method (returning a Promise<void>) to return control to Ink. It also implements [Symbol.asyncDispose], allowing it to be used with the await using pattern for safe, scoped terminal management.
  10. Configure CI rendering behavior

    master

    When Ink detects a CI environment (via the CI environment variable), it automatically adapts to prevent terminal issues:

    • It only renders the last frame on exit instead of continuous updates.
    • It stops listening to terminal resize events.

    To opt out of this behavior and use full terminal rendering in CI, set CI=false:

    CI=false node my-cli.js
  11. Test Ink components with ink-testing-library

    master

    Ink components can be tested using ink-testing-library. You can render a component and use the lastFrame() function to assert against the rendered output string.

    import React from 'react';
    import {Text} from 'ink';
    import {render} from 'ink-testing-library';
    
    const Test = () => <Text>Hello World</Text>;
    const {lastFrame} = render(<Test />);
    
    lastFrame() === 'Hello World'; //=> true