React bindings
Build terminal user interfaces using React with familiar patterns and components.
Installation
Quick start with bun and create-tui:
bun create tui --template react
Manual installation:
bun install @opentui/react @opentui/core react
Quick start
import { createCliRenderer } from "@opentui/core"
import { createRoot } from "@opentui/react"
function App() {
return <text>Hello, world!</text>
}
const renderer = await createCliRenderer()
createRoot(renderer).render(<App />)
TypeScript configuration
Configure your tsconfig.json:
{
"compilerOptions": {
"lib": ["ESNext", "DOM"],
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"jsxImportSource": "@opentui/react",
"strict": true,
"skipLibCheck": true
}
}
Components
OpenTUI React provides JSX intrinsic elements that map to core renderables:
Layout & display
<text>- Text display with styling<box>- Container with borders and layout<scrollbox>- Scrollable container<ascii-font>- ASCII art text
Input
<input>- Single-line text input<textarea>- Multi-line text input<select>- Selection list<tab-select>- Tab-based selection
Code & diff
<code>- Syntax-highlighted code<line-number>- Line numbers with diff/diagnostic support<diff>- Unified or split diff viewer<markdown>- Markdown rendering
Text modifiers
Use inside <text> components:
<span>- Inline styled text<strong>,<b>- Bold text<em>,<i>- Italic text<u>- Underlined text<br>- Line break<a>- Link text
API reference
createRoot(renderer)
Creates a React root for rendering into the terminal.
import { createCliRenderer } from "@opentui/core"
import { createRoot } from "@opentui/react"
const renderer = await createCliRenderer()
createRoot(renderer).render(<App />)
Hooks
useRenderer()
Access the OpenTUI renderer instance.
import { useRenderer } from "@opentui/react"
import { useEffect } from "react"
function App() {
const renderer = useRenderer()
useEffect(() => {
renderer.console.show()
console.log("Hello from console!")
}, [])
return <box />
}
useKeyboard(handler, options?)
Handle keyboard events.
import { useKeyboard } from "@opentui/react"
function App() {
useKeyboard((key) => {
if (key.name === "escape") {
process.exit(0)
}
})
return <text>Press ESC to exit</text>
}
To handle release events:
useKeyboard(
(event) => {
if (event.eventType === "release") {
console.log("Key released:", event.name)
} else {
console.log("Key pressed:", event.name)
}
},
{ release: true },
)
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-aware component</text>
}
useTerminalDimensions()
Get reactive terminal dimensions.
import { useTerminalDimensions } from "@opentui/react"
function App() {
const { width, height } = useTerminalDimensions()
return (
<text>
Terminal: {width}x{height}
</text>
)
}
useTimeline(options?)
Create and manage animations.
import { useTimeline } from "@opentui/react"
import { useEffect, useState } from "react"
function App() {
const [width, setWidth] = useState(0)
const timeline = useTimeline({
duration: 2000,
loop: false,
})
useEffect(() => {
timeline.add(
{ width },
{
width: 50,
duration: 2000,
ease: "linear",
onUpdate: (animation) => {
setWidth(animation.targets[0].width)
},
},
)
}, [])
return <box style={{ width, backgroundColor: "#6a5acd" }} />
}
Options:
duration- Animation duration in ms (default: 1000)loop- Whether to loop (default: false)autoplay- Auto-start (default: true)onComplete- Completion callbackonPause- Pause callback
Styling
Style components with props or the style prop:
// Direct props
<box backgroundColor="blue" padding={2}>
<text>Hello</text>
</box>
// Style prop
<box style={{ backgroundColor: "blue", padding: 2 }}>
<text>Hello</text>
</box>
Example: Login form
import { createCliRenderer } from "@opentui/core"
import { createRoot, useKeyboard } from "@opentui/react"
import { useCallback, useState } from "react"
function App() {
const [username, setUsername] = useState("")
const [password, setPassword] = useState("")
const [focused, setFocused] = useState<"username" | "password">("username")
const [status, setStatus] = useState("idle")
useKeyboard((key) => {
if (key.name === "tab") {
setFocused((prev) => (prev === "username" ? "password" : "username"))
}
})
const handleSubmit = useCallback(() => {
if (username === "admin" && password === "secret") {
setStatus("success")
} else {
setStatus("error")
}
}, [username, password])
return (
<box style={{ border: true, padding: 2, flexDirection: "column", gap: 1 }}>
<text fg="#FFFF00">Login Form</text>
<box title="Username" style={{ border: true, width: 40, height: 3 }}>
<input
placeholder="Enter username..."
onInput={setUsername}
onSubmit={handleSubmit}
focused={focused === "username"}
/>
</box>
<box title="Password" style={{ border: true, width: 40, height: 3 }}>
<input
placeholder="Enter password..."
onInput={setPassword}
onSubmit={handleSubmit}
focused={focused === "password"}
/>
</box>
<text fg={status === "success" ? "green" : status === "error" ? "red" : "#999"}>{status.toUpperCase()}</text>
</box>
)
}
const renderer = await createCliRenderer()
createRoot(renderer).render(<App />)
Component extension
Register custom renderables as JSX elements:
import { BoxRenderable, createCliRenderer, type BoxOptions, type RenderContext } from "@opentui/core"
import { createRoot, extend } from "@opentui/react"
class ConsoleButtonRenderable extends BoxRenderable {
private _label: string = "Button"
constructor(ctx: RenderContext, options: BoxOptions & { label?: string }) {
super(ctx, options)
if (options.label) this._label = options.label
this.borderStyle = "single"
this.padding = 2
}
get label(): string {
return this._label
}
set label(value: string) {
this._label = value
this.requestRender()
}
}
// Add TypeScript support
declare module "@opentui/react" {
interface OpenTUIComponents {
consoleButton: typeof ConsoleButtonRenderable
}
}
// Register the component
extend({ consoleButton: ConsoleButtonRenderable })
// Use in JSX
function App() {
return <consoleButton label="Click me!" style={{ border: true, backgroundColor: "green" }} />
}
const renderer = await createCliRenderer()
createRoot(renderer).render(<App />)
React DevTools
OpenTUI React supports React DevTools for debugging:
- Install:
bun add --dev react-devtools-core@7
- Start DevTools:
npx react-devtools@7
- Run with DEV flag:
DEV=true bun run your-app.ts