Renderer
The CliRenderer drives OpenTUI. It manages terminal output, handles input events, runs the rendering loop, and provides context for creating renderables.
Creating a renderer
Create a renderer with the async factory function:
import { createCliRenderer } from "@opentui/core"
const renderer = await createCliRenderer({
exitOnCtrlC: true,
targetFps: 30,
})
The factory function does three things:
- Loads the native Zig rendering library
- Configures terminal settings (mouse, keyboard protocol, and alternate screen)
- Returns an initialized
CliRendererinstance
Configuration options
| Option | Type | Default | Description |
|---|---|---|---|
exitOnCtrlC | boolean | true | Call renderer.destroy() when Ctrl+C is pressed |
exitSignals | NodeJS.Signals[] | see below | Signals that trigger cleanup (details) |
targetFps | number | 30 | Target frames per second for the render loop |
maxFps | number | 60 | Maximum FPS for immediate re-renders |
useMouse | boolean | true | Enable mouse input and tracking |
autoFocus | boolean | true | Focus nearest focusable on left click |
enableMouseMovement | boolean | true | Track mouse movement (not just clicks) |
useAlternateScreen | boolean | true | Use terminal alternate screen buffer |
consoleOptions | ConsoleOptions | - | Options for the built-in console overlay |
openConsoleOnError | boolean | true | Auto-open console when errors occur (dev only) |
onDestroy | () => void | - | Callback executed when renderer is destroyed |
The root renderable
Every renderer has a root property. It is a special RootRenderable at the top of the component tree:
import { Box, Text } from "@opentui/core"
// Add components to the root
renderer.root.add(Box({ width: 40, height: 10, borderStyle: "rounded" }, Text({ content: "Hello, OpenTUI!" })))
The root renderable fills the entire terminal and adjusts when you resize it.
Render loop control
You can use these control modes:
Automatic mode (default)
If you do not call start(), the renderer re-renders only when the component tree changes:
const renderer = await createCliRenderer()
renderer.root.add(Text({ content: "Static content" })) // Triggers render
Continuous mode
Call start() to run the render loop continuously at the target FPS:
renderer.start() // Start continuous rendering
renderer.stop() // Stop the render loop
Live rendering
For animations, call requestLive() to enable continuous rendering:
// Request live mode (increments internal counter)
renderer.requestLive()
// When animation completes, drop the request
renderer.dropLive()
Multiple components can request animations at the same time. The renderer stays live until all requests drop.
Pause and suspend
renderer.pause() // Pause rendering (use start() or requestLive() to run it again)
renderer.suspend() // Fully suspend (disables mouse, input, and raw mode)
renderer.resume() // Resume from suspended state
Key properties
| Property | Type | Description |
|---|---|---|
root | RootRenderable | Root of the component tree |
width | number | Current render width in columns |
height | number | Current render height in rows |
console | TerminalConsole | Built-in console overlay |
keyInput | KeyHandler | Keyboard input handler |
isRunning | boolean | Whether the render loop is active |
isDestroyed | boolean | Whether the renderer has been destroyed |
currentFocusedRenderable | Renderable | null | Currently focused component |
Events
The renderer emits events. You can listen for them:
// Terminal resized
renderer.on("resize", (width, height) => {
console.log(`Terminal size: ${width}x${height}`)
})
// Renderer destroyed
renderer.on("destroy", () => {
console.log("Renderer destroyed")
})
// Text selection completed
renderer.on("selection", (selection) => {
console.log("Selected text:", selection.getSelectedText())
})
Theme mode
OpenTUI can detect the terminal’s preferred color scheme (dark or light) when the terminal supports DEC mode 2031 color scheme updates. Read the current mode via renderer.themeMode and subscribe to theme_mode to react to changes. Possible values are "dark", "light", or null when unsupported, and no events fire in the unsupported case.
import { type ThemeMode } from "@opentui/core"
const mode = renderer.themeMode
renderer.on("theme_mode", (nextMode: ThemeMode) => {
console.log("Theme mode changed:", nextMode)
})
Cursor control
Use these methods to control the cursor position and style:
// Position and visibility
renderer.setCursorPosition(10, 5, true)
// Cursor style
//
// Available styles: "default", "block", "underline", "line"
// The default style is "default", which preserves the terminal's native cursor
// style instead of overriding it.
renderer.setCursorStyle({ style: "block", blinking: true }) // Blinking block
renderer.setCursorStyle({ style: "underline", blinking: false }) // Steady underline
renderer.setCursorStyle({ style: "line", blinking: true }) // Blinking line
renderer.setCursorStyle({ style: "default" }) // Reset to terminal's native cursor
// Cursor style with color
renderer.setCursorStyle({
style: "block",
blinking: true,
color: RGBA.fromHex("#FF0000"),
})
// Cursor style with mouse pointer
//
// Types of mouse pointers available: "default", "pointer", "text", "crosshair", "move",
// "not-allowed"
renderer.setCursorStyle({
style: "block",
blinking: false,
cursor: "pointer",
})
Input handling
Add custom input handlers:
renderer.addInputHandler((sequence) => {
if (sequence === "\x1b[A") {
// Up arrow - handle and consume
return true
}
return false // Let other handlers process
})
By default, addInputHandler() appends handlers to the chain and runs them after built-in handlers. Use prependInputHandler() to add a handler at the start of the chain and run it before built-in handlers.
Debug overlay
Use the debug overlay to show FPS, memory usage, and other stats:
renderer.toggleDebugOverlay()
// You can also configure it
import { DebugOverlayCorner } from "@opentui/core"
renderer.configureDebugOverlay({
enabled: true,
corner: DebugOverlayCorner.topRight,
})
Cleanup
Always destroy the renderer when you finish so you restore the terminal state:
renderer.destroy()
Destroying the renderer restores the terminal to its original state, disables mouse tracking, and cleans up resources.
Important: OpenTUI does not automatically clean up on process.exit or unhandled errors. This design gives you control. See Lifecycle for signal handling options and best practices.
Environment variables
| Variable | Description |
|---|---|
OTUI_USE_ALTERNATE_SCREEN | Override alternate screen setting |
OTUI_SHOW_STATS | Show debug overlay at startup |
OTUI_DEBUG | Enable debug input capture |
OTUI_NO_NATIVE_RENDER | Disable native rendering (for debugging) |
OTUI_DUMP_CAPTURES | Dump captured output when the renderer exits |
OTUI_OVERRIDE_STDOUT | Override stdout stream (for debugging) |
OTUI_USE_CONSOLE | Enable/disable built-in console |
SHOW_CONSOLE | Show console at startup |