Screens
maltty supports two command authoring models: handler-based commands for sequential log-and-exit flows, and screen-based commands for interactive React/Ink terminal UIs. Screen commands replace the handler function with a React component that receives parsed args as props.
Defining a screen
Use screen() from maltty/ui to define a screen command. It takes a render component and optional options, just like command() takes a handler:
See the screen() reference for all available fields.
Exit behavior
Screen commands support two exit modes:
'manual'(default) -- the screen stays alive until the component callsuseApp().exit()or the user presses Ctrl-C. Use for interactive dashboards and persistent TUIs.'auto'-- the runtime calls exit automatically once the component unmounts or the render tree settles. Use for status displays that render once and complete.
Basic example (auto exit)
Interactive example (manual exit)
Context hooks
Inside screen components, runtime context is accessed via hooks instead of the ctx object. Hooks like useConfig(), useMeta(), useStore(), and useApp() replace the corresponding ctx properties.
These hooks are only available inside components rendered by screen(). They throw if used outside the MalttyProvider.
ctx.log, ctx.prompts, ctx.status, ctx.colors, and ctx.format are not available in screen components. Screen commands use React components and Ink primitives for all output.
See the screen() reference for the full hooks and components API.
Middleware and screens
Screen commands skip the middleware pipeline entirely. When a command has a render property, the runtime invokes the render function directly instead of running middleware and then a handler.
This means middleware like auth(), report(), or custom middleware will not run for screen commands. If a screen needs authenticated data, fetch it inside the component or use a wrapper pattern.
When to use screen() vs command()
File conventions
- Use
.tsxextension for files containing screen commands or React components. - Use
.tsextension for handler-only commands. - Command-private components go in a
_components/subdirectory (leading underscore prevents autoloader from treating them as commands). - Name React components with PascalCase.
- Import all Ink primitives from
maltty/ui, never directly frominkor@inkjs/ui.
Developing components with stories
maltty includes a Storybook-like TUI for developing and previewing screen components in isolation. Define .stories.tsx files alongside your components with story() or stories() from maltty/stories, then run maltty stories to browse them with live preview and an interactive props editor.