9.5 KiB
Solid API Reference
Rendering
render(node, rendererOrConfig?)
Renders a Solid component tree into a CLI renderer.
import { render } from "@opentui/solid"
// Simple usage - creates renderer automatically
render(() => <App />)
// With config
render(() => <App />, {
exitOnCtrlC: false,
targetFps: 30,
})
// With existing renderer
import { createCliRenderer } from "@opentui/core"
const renderer = await createCliRenderer()
render(() => <App />, renderer)
testRender(node, options?)
Create a test renderer for snapshots and tests.
import { testRender } from "@opentui/solid"
const testSetup = await testRender(() => <App />, {
width: 40,
height: 10,
})
// Access test utilities
testSetup.snapshot() // Get current render
testSetup.renderer // Access renderer
extend(components)
Register custom renderables as JSX intrinsic elements.
import { extend } from "@opentui/solid"
import { CustomRenderable } from "./custom"
extend({
custom: CustomRenderable,
})
// Now usable in JSX
<custom prop="value" />
getComponentCatalogue()
Returns the current component catalogue.
import { getComponentCatalogue } from "@opentui/solid"
const catalogue = getComponentCatalogue()
console.log(Object.keys(catalogue))
Hooks
useRenderer()
Access the OpenTUI renderer instance.
import { useRenderer } from "@opentui/solid"
import { onMount } from "solid-js"
function App() {
const renderer = useRenderer()
onMount(() => {
console.log(`Terminal: ${renderer.width}x${renderer.height}`)
renderer.console.show()
// Access theme mode (dark/light based on terminal settings)
console.log(`Theme: ${renderer.themeMode}`) // "dark" | "light" | null
})
return <text>Hello</text>
}
// Listen for theme mode changes
function ThemedApp() {
const renderer = useRenderer()
const [theme, setTheme] = createSignal(renderer.themeMode ?? "dark")
onMount(() => {
renderer.on("theme_mode", (mode: "dark" | "light") => setTheme(mode))
})
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/solid"
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] = createSignal(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 }
)
return <text>Pressed: {Array.from(pressed()).join(", ")}</text>
}
usePaste(handler)
Handle paste events. Receives a PasteEvent with raw bytes.
import { usePaste } from "@opentui/solid"
import { decodePasteBytes } from "@opentui/core"
function PasteHandler() {
usePaste((event) => {
const text = decodePasteBytes(event.bytes)
console.log("Pasted:", text)
})
return <text>Paste something</text>
}
onResize(callback)
Handle terminal resize events.
import { onResize } from "@opentui/solid"
function App() {
onResize((width, height) => {
console.log(`Resized to ${width}x${height}`)
})
return <text>Resize the terminal</text>
}
useTerminalDimensions()
Get reactive terminal dimensions.
import { useTerminalDimensions } from "@opentui/solid"
function ResponsiveLayout() {
const dimensions = useTerminalDimensions()
return (
<box flexDirection={dimensions().width > 80 ? "row" : "column"}>
<text>Width: {dimensions().width}</text>
<text>Height: {dimensions().height}</text>
</box>
)
}
onFocus(callback) / onBlur(callback)
Handle terminal window focus and blur events. React exposes equivalent
useFocus and useBlur hooks.
import { onFocus, onBlur } from "@opentui/solid"
function App() {
onFocus(() => {
console.log("Terminal window gained focus")
})
onBlur(() => {
console.log("Terminal window lost focus")
})
return <text>Focus/blur tracking</text>
}
These hooks fire when the terminal emulator window gains or loses operating system focus. The renderer deduplicates events (won't re-emit the same focus state).
useSelectionHandler(handler)
Handle text selection events. Fires when the user finishes a mouse selection
(mouse-up). React exposes an equivalent useSelectionHandler hook.
import { useSelectionHandler } from "@opentui/solid"
import type { Selection } from "@opentui/core"
function SelectableText() {
const [selected, setSelected] = createSignal("")
const renderer = useRenderer()
useSelectionHandler((selection: Selection) => {
const text = selection.getSelectedText()
if (text) {
setSelected(text)
renderer.copyToClipboardOSC52(text)
}
})
return (
<box flexDirection="column">
<text selectable>Select this text with your mouse</text>
<text fg="#888">Selected: {selected()}</text>
</box>
)
}
The Selection object aggregates selected text from all selectable renderables in the tree. See keyboard/REFERENCE.md (selection) for full details on the selection API and traversal model.
useTimeline(options?)
Create animations with the timeline system.
import { useTimeline } from "@opentui/solid"
import { createSignal, onMount } from "solid-js"
function AnimatedBox() {
const [width, setWidth] = createSignal(0)
const timeline = useTimeline({
duration: 2000,
loop: false,
})
onMount(() => {
timeline.add(
{ width: 0 },
{
width: 50,
duration: 2000,
ease: "outQuad",
onUpdate: (anim) => {
setWidth(Math.round(anim.targets[0].width))
},
}
)
})
return <box style={{ width: width(), height: 3, backgroundColor: "#6a5acd" }} />
}
Components
Full props for every component live in the shared components references. This section only covers what is Solid-specific; read the linked category file for the complete prop list.
JSX Element Names (Solid uses underscores)
Multi-word elements use underscores (not hyphens like React):
| 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 (Solid-specific)
<text fg="#FFFFFF" bg="#000000" selectable>
<span fg="red">Red</span> <strong>Bold</strong> <em>Italic</em> <u>Underline</u>
</text>
Note
: Do NOT use
bold,italic,underlineas props on<text>. Use nested modifier tags like<strong>,<em>,<u>instead.
Reactive Values + onInput (Solid-specific)
Solid uses onInput (not React's onChange) for single-line inputs, and values are
signals. Use <For> inside <scrollbox> for lists:
<input value={value()} onInput={setValue} focused />
// Textarea is not a controlled input. Seed it with initialValue and use a ref
// plus onContentChange to read textarea.plainText.
<textarea initialValue={text()} ref={setTextarea} onContentChange={() => setText(textarea.plainText)} />
// select/tab_select still use onChange (navigate) / onSelect (Enter)
<select options={opts} onChange={(i, opt) => setSelected(opt)} focused />
<scrollbox focused>
<For each={items()}>{(item) => <text>{item}</text>}</For>
</scrollbox>
Scrollbox takes the same nested style object as React — see
containers.md.
Control Flow
Solid's control flow components work with OpenTUI:
For
import { For } from "solid-js"
<For each={items()}>
{(item, index) => (
<box key={index()}>
<text>{item.name}</text>
</box>
)}
</For>
Show
import { Show } from "solid-js"
<Show when={isVisible()} fallback={<text>Hidden</text>}>
<text>Visible content</text>
</Show>
Switch/Match
import { Switch, Match } from "solid-js"
<Switch>
<Match when={status() === "loading"}>
<text>Loading...</text>
</Match>
<Match when={status() === "error"}>
<text fg="red">Error!</text>
</Match>
<Match when={status() === "success"}>
<text fg="green">Success!</text>
</Match>
</Switch>
Index
import { Index } from "solid-js"
<Index each={items()}>
{(item, index) => (
<text>{index}: {item().name}</text>
)}
</Index>
Special Components
Portal
import { Portal } from "@opentui/solid"
<Portal mount={targetNode}>
<box>Portal content</box>
</Portal>
Dynamic
import { Dynamic } from "@opentui/solid"
<Dynamic
component={isMultiline() ? "textarea" : "input"}
placeholder="Enter text..."
focused
/>