React bindings for @effect-tui/core
npm install @effect-tui/reactReact renderer for terminal UIs with spring animations.
``bash`
bun add @effect-tui/react @effect-tui/core effect react
`tsx
// tsconfig.json: { "compilerOptions": { "jsxImportSource": "@effect-tui/react" } }
import { render, useQuit, useShortcut } from "@effect-tui/react"
function App() {
const [count, setCount] = useState(0)
const quit = useQuit()
useShortcut({
q: () => quit(),
space: () => setCount((c) => c + 1),
})
return (
)
}
render(
`
Enable hot reload by passing dev: true and importMeta:
`tsx
export default function App() { ... }
render(
`
Note: When dev: true, the entry module's default export is re-imported
on each change. Keep your app as the default export for reliable HMR.
- - Vertical flex layout
- - Horizontal flex layout
- - Text with optional fg/bg colors
- - Container with optional border
-
Canvas draw context:
- text(x, y, str, style?)fillRect(x, y, w, h, char?, style?)
- box(x, y, w, h, opts?)
- clear()
- style(style?)
- → style id for reusecell(x, y, cp, style?, width?)
- cells(cells[])
-
- useKeyboard(callback) - Handle keyboard inputuseShortcut(shortcuts, options?)
- - Map key strings to handlersusePaste(callback)
- - Handle bracketed paste events (callback receives { text, preventDefault? })useQuit()
- - Request a clean exit (restores terminal state)useTerminalSize()
- - Get terminal dimensions (re-renders on resize)useSpring(initial, options)
- - Spring animationuseSprings(count, fn)
- - Multiple springsuseColorSpring(color, options)
- - Animate colors
- TextInput - Single-line input (pasting inserts normalized text)MultilineTextInput
- - Multi-line input (pasting preserves newlines)
`tsx
import { useSpring, useSpringRenderer, useRenderer } from "@effect-tui/react"
function AnimatedBox() {
const renderer = useRenderer()
useSpringRenderer(renderer)
const [xMv, setX] = useSpring(0, { visualDuration: 0.35 })
useKeyboard((key) => {
if (key.name === "right") setX(20)
if (key.name === "left") setX(0)
})
return (
Each frame (60fps default):
1. Measure - Host tree calculates sizes bottom-up
2. Layout - Host tree assigns positions top-down
3. Render - Each host writes to CellBuffer
4. Diff - Compare with previous buffer, find changed lines
5. Write - Single stdout.write() with ANSI sequences
``
React JSX → Reconciler → Host Tree → CellBuffer → Terminal
Use renderTUI() for integration tests:
`tsx
import { renderTUI } from "@effect-tui/react/test"
it("renders and handles input", () => {
const { lastFrame, sendKey, renderNow, unmount } = renderTUI(
expect(lastFrame()).toContain("Count: 0")
sendKey({ name: "up" })
renderNow()
expect(lastFrame()).toContain("Count: 1")
unmount()
})
`
- createRenderer(options?) - Create renderer instancecreateRoot(renderer)
- - Create React rootrender(element, options?)
- - One-liner convenience API
importMeta is required whenever you pass the options object.
`ts
type RenderOptions = RendererOptions & {
dev?: boolean
importMeta: ImportMetaLike
}
interface RendererOptions {
fps?: number // Default: 60
mode?: "fullscreen" | "inline"
exitOnCtrlC?: boolean // Default: true
handleSignals?: boolean // Default: true (signals + process exit cleanup)
manualMode?: boolean // For testing
}
`
- PROFILE_TUI=1 - Enable profiling (writes to tui-profile.txt)EFFECT_TUI_EDITOR
- / EDITOR` - Editor for trace visualization
MIT