8.2 KiB
React API Reference
Requirements:
@opentui/reactusesreact-reconciler0.33 and requires React ≥ 19.2. Runbun add react@latest react-dom@latestbefore upgrading.
Rendering
createRoot(renderer)
Creates a React root for rendering.
import { createCliRenderer } from "@opentui/core"
import { createRoot } from "@opentui/react"
const renderer = await createCliRenderer({
exitOnCtrlC: false, // Handle Ctrl+C yourself
})
const root = createRoot(renderer)
root.render(<App />)
Hooks
useRenderer()
Access the OpenTUI renderer instance.
import { useRenderer } from "@opentui/react"
import { useEffect } from "react"
function App() {
const renderer = useRenderer()
useEffect(() => {
// Access renderer properties
console.log(`Terminal: ${renderer.width}x${renderer.height}`)
// Show debug console
renderer.console.show()
// Access theme mode (dark/light based on terminal settings)
console.log(`Theme: ${renderer.themeMode}`) // "dark" | "light" | null
}, [renderer])
return <text>Hello</text>
}
// Listen for theme mode changes
function ThemedApp() {
const renderer = useRenderer()
const [theme, setTheme] = useState(renderer.themeMode ?? "dark")
useEffect(() => {
const handler = (mode: "dark" | "light") => setTheme(mode)
renderer.on("theme_mode", handler)
return () => renderer.off("theme_mode", handler)
}, [renderer])
return (
<box backgroundColor={theme === "dark" ? "#1a1a2e" : "#ffffff"}>
<text fg={theme === "dark" ? "#fff" : "#000"}>
Current theme: {theme}
</text>
</box>
)
}
useKeyboard(handler, options?)
Handle keyboard events.
import { useKeyboard, useRenderer } from "@opentui/react"
function App() {
const renderer = useRenderer()
useKeyboard((key) => {
if (key.name === "escape") {
renderer.destroy() // Never use process.exit() directly!
}
if (key.ctrl && key.name === "s") {
saveDocument()
}
})
return <text>Press ESC to exit</text>
}
// With release events
function GameControls() {
const [pressed, setPressed] = useState(new Set<string>())
useKeyboard(
(event) => {
setPressed(keys => {
const newKeys = new Set(keys)
if (event.eventType === "release") {
newKeys.delete(event.name)
} else {
newKeys.add(event.name)
}
return newKeys
})
},
{ release: true } // Include release events
)
return <text>Pressed: {Array.from(pressed).join(", ")}</text>
}
Options:
release?: boolean- Include key release events (default: false)
KeyEvent properties:
name: string- Key name ("a", "escape", "f1", etc.)sequence: string- Raw escape sequencectrl: boolean- Ctrl modifiershift: boolean- Shift modifiermeta: boolean- Alt modifieroption: boolean- Option modifier (macOS)eventType: "press" | "release" | "repeat"repeated: boolean- Key is being held
useOnResize(callback)
Handle terminal resize events.
import { useOnResize } from "@opentui/react"
function App() {
useOnResize((width, height) => {
console.log(`Resized to ${width}x${height}`)
})
return <text>Resize the terminal</text>
}
useTerminalDimensions()
Get reactive terminal dimensions.
import { useTerminalDimensions } from "@opentui/react"
function ResponsiveLayout() {
const { width, height } = useTerminalDimensions()
return (
<box flexDirection={width > 80 ? "row" : "column"}>
<box flexGrow={1}>
<text>Width: {width}</text>
</box>
<box flexGrow={1}>
<text>Height: {height}</text>
</box>
</box>
)
}
useTimeline(options?)
Create animations with the timeline system.
import { useTimeline } from "@opentui/react"
import { useEffect, useState } from "react"
function AnimatedBox() {
const [width, setWidth] = useState(0)
const timeline = useTimeline({
duration: 2000,
loop: false,
})
useEffect(() => {
timeline.add(
{ width: 0 },
{
width: 50,
duration: 2000,
ease: "outQuad",
onUpdate: (anim) => {
setWidth(Math.round(anim.targets[0].width))
},
}
)
}, [timeline])
return <box style={{ width, height: 3, backgroundColor: "#6a5acd" }} />
}
Options:
duration?: number- Default duration (ms)loop?: boolean- Loop the timelineautoplay?: boolean- Auto-start (default: true)onComplete?: () => void- Completion callbackonPause?: () => void- Pause callback
Timeline methods:
add(target, properties, startTime?)- Add animationplay()- Start playbackpause()- Pause playbackrestart()- Restart from beginning
usePaste(handler)
Subscribe to bracketed-paste events. handler receives a PasteEvent with raw
bytes (decode with decodePasteBytes). The callback is kept stable across
renders.
import { usePaste } from "@opentui/react"
import { decodePasteBytes } from "@opentui/core"
function Editor() {
usePaste((event) => {
console.log("Pasted:", decodePasteBytes(event.bytes))
})
return <textarea focused />
}
useFocus(handler) / useBlur(handler)
Fire when the terminal window gains or loses OS focus.
import { useFocus, useBlur } from "@opentui/react"
useFocus(() => console.log("Terminal gained focus"))
useBlur(() => console.log("Terminal lost focus"))
useSelectionHandler(handler)
Fire when the user finishes a text selection (mouse-up). handler receives a
Selection (from @opentui/core); use selection.getSelectedText().
import { useSelectionHandler } from "@opentui/react"
useSelectionHandler((selection) => {
console.log("Selected:", selection.getSelectedText())
})
These four hooks (
usePaste,useFocus,useBlur,useSelectionHandler) mirror the Solid hooks and are available from@opentui/react.
Components
Full props for every component live in the shared components references. This section only covers what is React-specific; read the linked category file for the complete prop list.
JSX Element Names
React uses hyphenated tag names. Full props are in the linked file:
| Element | Full props |
|---|---|
<text>, <span>, <strong>, <em>, <u>, <a>, <br>, <image>, <time-to-first-draw> |
text-display.md |
<box>, <scrollbox> |
containers.md |
<input>, <textarea>, <select>, <tab-select> |
inputs.md |
<code>, <line-number>, <diff>, <markdown> |
code-diff.md |
<ascii-font> |
text-display.md |
Text Styling Uses Nested Tags (React-specific)
Style text with nested modifier elements, not props:
<text fg="#FFFFFF" bg="#000000" selectable>
<span fg="red">Red</span> <strong>Bold</strong> <em>Italic</em> <u>Underline</u>
<br />
<a href="https://...">Link</a>
</text>
Note
: Do NOT use
bold,italic,underlineas props on<text>. Use nested modifier tags like<strong>,<em>,<u>instead.
Controlled Inputs (React-specific)
Single-line inputs are controlled with value + onChange, and use the
focused prop to receive keyboard input. Textarea is imperative: use
initialValue, keep a ref, and read plainText from onContentChange.
<input value={value} onChange={setValue} focused />
<textarea initialValue={text} ref={textareaRef} onContentChange={() => setText(textareaRef.current!.plainText)} />
// Select/tab-select: onChange fires on navigation, onSelect on Enter
<select options={opts} onChange={(i, opt) => setSel(opt)} focused />
Scrollbox style Nesting (React-specific)
<scrollbox> takes a nested style object (rootOptions, wrapperOptions,
viewportOptions, contentOptions, scrollbarOptions). See
containers.md for the full structure.
Type Exports
import type {
// Component props
TextProps,
BoxProps,
InputProps,
SelectProps,
// Hook types
KeyEvent,
// From core
CliRenderer,
} from "@opentui/react"