Components
Overview
Standards for React/Ink components used in maltty CLI commands. Commands can use screen() from maltty/ui to build interactive terminal UIs with React components. These rules govern file conventions, component structure, colocation, and when to choose screen mode over handler mode.
Rules
Use .tsx for Files with JSX
Command files that contain JSX must use the .tsx extension. Files without JSX use .ts. The autoloader discovers both extensions.
Correct
Incorrect
Name Components with PascalCase
All React function components use PascalCase names. This applies to both shared and command-private components.
Correct
Incorrect
Colocate Props Interfaces
Define props interfaces in the same file as the component. Use readonly on all properties. Name them {ComponentName}Props.
Correct
Incorrect
Use _components/ for Command-Private Components
Components used by a single command live in a _components/ directory next to the command file. The leading underscore prevents the autoloader from treating them as commands.
Correct
Use src/ui/ for Shared Components
Components used by multiple commands live in src/ui/. Import them with the @/ alias.
Correct
Choose screen() for Interactive or Stateful UI
Use screen() when the command needs React state, hooks, dynamic updates, or complex layout. Use command() with a handler for sequential operations that log output and exit.
Correct -- screen mode
Correct -- handler mode
The screen() Factory Owns the Lifecycle
The screen() factory handles Ink rendering, the MalttyProvider, and exit behavior. The component receives parsed args as props. Runtime context (config, meta, store) is available via hooks.
Available hooks inside screen components:
Exit Behavior
Screens default to 'manual' exit — the component stays alive until useApp().exit() is called or the user presses Ctrl-C. Use exit: 'auto' for screens that render once and exit.
No let at Module Level
The no let rule still applies at module level in .tsx files. Inside React components, useState and other hooks manage mutable state -- this is the expected pattern for component-local state.
Correct
Incorrect
Use Ink Primitives from maltty/ui
Import all Ink primitives and @inkjs/ui components from maltty/ui. Do not import from ink or @inkjs/ui directly.
Correct
Incorrect
Resources
References
- Coding Style -- Constraints (no classes, no let, no throw, etc.)
- Design Patterns -- Factories, pipelines, composition
- Naming -- Naming conventions