> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/anomalyco/opentui/llms.txt
> Use this file to discover all available pages before exploring further.

# SolidJS

> Build terminal UIs with SolidJS using OpenTUI's Solid reconciler

OpenTUI Solid is a custom SolidJS reconciler that brings fine-grained reactivity to terminal user interfaces. Build performant CLI apps with Solid's reactive primitives.

## Installation

<CodeGroup>
  ```bash npm theme={null}
  npm install solid-js @opentui/solid
  ```

  ```bash yarn theme={null}
  yarn add solid-js @opentui/solid
  ```

  ```bash pnpm theme={null}
  pnpm add solid-js @opentui/solid
  ```

  ```bash bun theme={null}
  bun add solid-js @opentui/solid
  ```
</CodeGroup>

## Setup

### 1. Configure TypeScript

Add JSX configuration to your `tsconfig.json`:

```json tsconfig.json theme={null}
{
  "compilerOptions": {
    "jsx": "preserve",
    "jsxImportSource": "@opentui/solid"
  }
}
```

### 2. Add Preload Script

Configure `bunfig.toml` to preload the Solid plugin:

```toml bunfig.toml theme={null}
preload = ["@opentui/solid/preload"]
```

### 3. Create Your App

```tsx index.tsx theme={null}
import { render } from "@opentui/solid"

function App() {
  return <text>Hello, OpenTUI!</text>
}

render(() => <App />)
```

### 4. Run Your App

```bash theme={null}
bun index.tsx
```

## Building for Production

Use [Bun.build](https://bun.sh/docs/bundler) with the Solid plugin:

```ts build.ts theme={null}
import solidPlugin from "@opentui/solid/bun-plugin"

await Bun.build({
  entrypoints: ["./index.tsx"],
  target: "bun",
  outdir: "./build",
  plugins: [solidPlugin],
  compile: {
    target: "bun-darwin-arm64",
    outfile: "app-macos",
  },
})
```

## Rendering

### render

Render a Solid component tree into a CLI renderer:

```tsx theme={null}
import { createCliRenderer } from "@opentui/core"
import { render } from "@opentui/solid"

// With default renderer
render(() => <App />)

// With custom renderer
const renderer = await createCliRenderer({
  exitOnCtrlC: false,
})

render(() => <App />, renderer)

// With renderer config
render(() => <App />, {
  exitOnCtrlC: false,
})
```

**Parameters:**

* `node` - Function returning a JSX element
* `rendererOrConfig?` - `CliRenderer` instance or `CliRendererConfig`

### testRender

Create a test renderer for snapshots and interaction tests:

```tsx theme={null}
import { testRender } from "@opentui/solid"

const testSetup = await testRender(() => <App />, {
  width: 40,
  height: 10,
})

// Access the renderer
testSetup.renderer

// Simulate keyboard input
testSetup.sendKey({ name: "return" })

// Get rendered output
const snapshot = testSetup.renderer.toString()
```

## Reactive Hooks

### useRenderer

Access the OpenTUI renderer instance:

```tsx theme={null}
import { useRenderer } from "@opentui/solid"
import { onMount } from "solid-js"

function App() {
  const renderer = useRenderer()

  onMount(() => {
    renderer.console.show()
    console.log("Hello from console!")
  })

  return <box />
}
```

### useKeyboard

Handle keyboard input with press and release events:

```tsx theme={null}
import { useKeyboard } from "@opentui/solid"
import { createSignal } from "solid-js"

function App() {
  const [pressed, setPressed] = createSignal<Set<string>>(new Set())

  useKeyboard((event) => {
    setPressed(prev => {
      const next = new Set(prev)
      if (event.eventType === "release") {
        next.delete(event.name)
      } else {
        next.add(event.name)
      }
      return next
    })
  }, { release: true })

  return (
    <text>
      Pressed: {Array.from(pressed()).join(", ") || "none"}
    </text>
  )
}
```

**Parameters:**

* `callback` - Function receiving a `KeyEvent` object
* `options?` - Optional configuration:
  * `release?: boolean` - Include key release events (default: `false`)

<Note>
  By default, only press events are received (including repeats with `repeated: true`). Set `options.release = true` to also receive release events.
</Note>

### onResize

Handle terminal resize events:

```tsx theme={null}
import { onResize, useRenderer } from "@opentui/solid"
import { onMount } from "solid-js"

function App() {
  const renderer = useRenderer()

  onMount(() => {
    renderer.console.show()
  })

  onResize((width, height) => {
    console.log(`Resized to ${width}x${height}`)
  })

  return <text>Resize-aware component</text>
}
```

### useTerminalDimensions

Get current terminal dimensions with automatic updates:

```tsx theme={null}
import { useTerminalDimensions } from "@opentui/solid"

function App() {
  const dimensions = useTerminalDimensions()

  return (
    <box>
      <text>Terminal: {dimensions().width}x{dimensions().height}</text>
      <box
        style={{
          width: Math.floor(dimensions().width / 2),
          height: Math.floor(dimensions().height / 3)
        }}
      >
        <text>Half-width, third-height box</text>
      </box>
    </box>
  )
}
```

**Returns:** Signal containing `{ width: number, height: number }`

### usePaste

Handle paste events from the terminal:

```tsx theme={null}
import { usePaste } from "@opentui/solid"
import { createSignal } from "solid-js"

function App() {
  const [pastedText, setPastedText] = createSignal("")

  usePaste((event) => {
    setPastedText(event.text)
  })

  return <text>Last paste: {pastedText()}</text>
}
```

### useSelectionHandler

Handle text selection events:

```tsx theme={null}
import { useSelectionHandler } from "@opentui/solid"
import { createSignal } from "solid-js"

function App() {
  const [selection, setSelection] = createSignal<string | null>(null)

  useSelectionHandler((sel) => {
    setSelection(sel.text)
  })

  return <text>Selected: {selection() || "none"}</text>
}
```

### useTimeline

Create and manage animations using OpenTUI's timeline system:

```tsx theme={null}
import { useTimeline } from "@opentui/solid"
import { createSignal, onMount } from "solid-js"

function App() {
  const [width, setWidth] = createSignal(0)

  const timeline = useTimeline({
    duration: 2000,
    loop: false,
  })

  onMount(() => {
    timeline.add(
      { width: width() },
      {
        width: 50,
        duration: 2000,
        ease: "linear",
        onUpdate: (animation) => {
          setWidth(animation.targets[0].width)
        },
      }
    )
  })

  return <box style={{ width: width(), backgroundColor: "#6a5acd" }} />
}
```

**Parameters:**

* `options?` - Optional `TimelineOptions`:
  * `duration?: number` - Animation duration in ms (default: 1000)
  * `loop?: boolean` - Whether to loop (default: false)
  * `autoplay?: boolean` - Auto-start timeline (default: true)
  * `onComplete?: () => void` - Completion callback
  * `onPause?: () => void` - Pause callback

**Returns:** `Timeline` instance with methods:

* `add(target, properties, startTime)` - Add animation
* `play()` - Start timeline
* `pause()` - Pause timeline
* `restart()` - Restart from beginning

## Components

OpenTUI Solid provides intrinsic JSX elements that map to OpenTUI renderables:

### Layout & Display

* `<text>` - Display styled text
* `<box>` - Container with borders and layout
* `<scrollbox>` - Scrollable container
* `<ascii_font>` - ASCII art text renderer

<Note>
  Notice the underscore in `ascii_font` - Solid uses underscores for multi-word element names.
</Note>

### Input Components

* `<input>` - Single-line text input
* `<textarea>` - Multi-line text input
* `<select>` - Dropdown selection
* `<tab_select>` - Tab-based selection

### Code & Diff

* `<code>` - Syntax-highlighted code blocks
* `<line_number>` - Line-numbered code with diff/diagnostics
* `<diff>` - Unified or split diff viewer

### Text Modifiers

<Warning>
  These elements must be used inside a `<text>` component:
</Warning>

* `<span>` - Inline styled text
* `<strong>`, `<b>` - Bold text
* `<em>`, `<i>` - Italic text
* `<u>` - Underlined text
* `<br>` - Line break
* `<a>` - Link with `href` attribute

See the [Components](/components/overview) section for detailed documentation on each component.

## Advanced Components

### Portal

Render children into a different mount point, useful for overlays and modals:

```tsx theme={null}
import { Portal, useRenderer } from "@opentui/solid"

function App() {
  const renderer = useRenderer()

  return (
    <>
      <box border>
        <text>Main content</text>
      </box>

      <Portal mount={renderer.root}>
        <box border title="Overlay">
          <text>This is rendered at the root level</text>
        </box>
      </Portal>
    </>
  )
}
```

### Dynamic

Render arbitrary intrinsic elements or components dynamically:

```tsx theme={null}
import { Dynamic } from "@opentui/solid"
import { createSignal } from "solid-js"

function App() {
  const [isMultiline, setIsMultiline] = createSignal(false)

  return (
    <box>
      <Dynamic
        component={isMultiline() ? "textarea" : "input"}
        placeholder="Type here..."
        focused
      />
      
      <text onClick={() => setIsMultiline(!isMultiline())}>
        Toggle multiline
      </text>
    </box>
  )
}
```

## Examples

### Counter with Reactive Updates

```tsx theme={null}
import { render } from "@opentui/solid"
import { createSignal, onCleanup } from "solid-js"

function App() {
  const [count, setCount] = createSignal(0)

  const interval = setInterval(() => {
    setCount(c => c + 1)
  }, 1000)

  onCleanup(() => clearInterval(interval))

  return (
    <box title="Counter" style={{ padding: 2 }}>
      <text fg="#00FF00">Count: {count()}</text>
    </box>
  )
}

render(() => <App />)
```

### Interactive Todo List

```tsx theme={null}
import { render, useKeyboard } from "@opentui/solid"
import { createSignal, For } from "solid-js"

type Todo = { id: number; text: string; done: boolean }

function App() {
  const [todos, setTodos] = createSignal<Todo[]>([
    { id: 1, text: "Learn OpenTUI", done: false },
    { id: 2, text: "Build a CLI app", done: false },
  ])
  const [selected, setSelected] = createSignal(0)

  useKeyboard((key) => {
    if (key.name === "up") {
      setSelected(s => Math.max(0, s - 1))
    } else if (key.name === "down") {
      setSelected(s => Math.min(todos().length - 1, s + 1))
    } else if (key.name === "space") {
      setTodos(todos => todos.map((t, i) =>
        i === selected() ? { ...t, done: !t.done } : t
      ))
    }
  })

  return (
    <box title="Todo List" border style={{ padding: 1, flexDirection: "column" }}>
      <For each={todos()}>
        {(todo, i) => (
          <text
            fg={i() === selected() ? "#00FF00" : "#FFFFFF"}
            style={{ backgroundColor: i() === selected() ? "#333333" : undefined }}
          >
            {todo.done ? "[x]" : "[ ]"} {todo.text}
          </text>
        )}
      </For>
      <text fg="#666666">↑↓ to navigate, Space to toggle</text>
    </box>
  )
}

render(() => <App />)
```

### Animated Progress Bar

```tsx theme={null}
import { render, useTimeline } from "@opentui/solid"
import { createSignal, onMount } from "solid-js"

function App() {
  const [progress, setProgress] = createSignal(0)

  const timeline = useTimeline({
    duration: 3000,
    loop: true,
  })

  onMount(() => {
    timeline.add(
      { value: 0 },
      {
        value: 100,
        duration: 3000,
        ease: "linear",
        onUpdate: (anim) => {
          setProgress(Math.floor(anim.targets[0].value))
        },
      }
    )
  })

  return (
    <box title="Loading..." border style={{ padding: 1, flexDirection: "column" }}>
      <text>Progress: {progress()}%</text>
      <box style={{ width: 50, height: 1, backgroundColor: "#333333" }}>
        <box
          style={{
            width: Math.floor(50 * progress() / 100),
            height: 1,
            backgroundColor: "#00FF00"
          }}
        />
      </box>
    </box>
  )
}

render(() => <App />)
```

## Extending Components

Register custom renderables as JSX intrinsic elements:

```tsx theme={null}
import {
  BoxRenderable,
  OptimizedBuffer,
  RGBA,
  type BoxOptions,
  type RenderContext,
} from "@opentui/core"
import { render, extend } from "@opentui/solid"

class CustomButton extends BoxRenderable {
  private _label: string

  constructor(ctx: RenderContext, options: BoxOptions & { label?: string }) {
    super(ctx, {
      border: true,
      borderStyle: "single",
      minHeight: 3,
      ...options,
    })
    this._label = options.label ?? "Button"
  }

  protected renderSelf(buffer: OptimizedBuffer): void {
    super.renderSelf(buffer)

    const centerX = this.x + Math.floor(this.width / 2 - this._label.length / 2)
    const centerY = this.y + Math.floor(this.height / 2)

    buffer.drawText(this._label, centerX, centerY, RGBA.fromInts(255, 255, 255, 255))
  }
}

// Register the component
extend({ custom_button: CustomButton })

// Add TypeScript support
declare module "@opentui/solid" {
  namespace JSX {
    interface IntrinsicElements {
      custom_button: BoxOptions & { label?: string }
    }
  }
}

// Use in JSX
function App() {
  return (
    <box>
      <custom_button label="Click me!" style={{ backgroundColor: "blue" }} />
    </box>
  )
}

render(() => <App />)
```

## Utilities

### getComponentCatalogue

Get the current component catalogue that powers JSX tag lookup:

```tsx theme={null}
import { getComponentCatalogue } from "@opentui/solid"

const catalogue = getComponentCatalogue()
console.log(Object.keys(catalogue))
// ["box", "text", "input", "textarea", ...]
```

## Performance Tips

<AccordionGroup>
  <Accordion title="Use fine-grained reactivity">
    Solid's reactivity system updates only what changes. Avoid wrapping large component trees in effects - let Solid track dependencies automatically.

    ```tsx theme={null}
    // Good - fine-grained
    <text>Count: {count()}</text>

    // Avoid - coarse-grained
    createEffect(() => {
      return <text>Count: {count()}</text>
    })
    ```
  </Accordion>

  <Accordion title="Destructure props in render, not component body">
    Destructuring props at the component level breaks reactivity. Access props directly or destructure in JSX.

    ```tsx theme={null}
    // Good
    function Component(props) {
      return <text>{props.value}</text>
    }

    // Bad - loses reactivity
    function Component(props) {
      const { value } = props
      return <text>{value}</text>
    }
    ```
  </Accordion>

  <Accordion title="Use Index for static lists">
    When rendering lists where items don't change identity, use `<Index>` instead of `<For>` for better performance.

    ```tsx theme={null}
    import { Index } from "solid-js"

    <Index each={items()}>
      {(item, i) => <text>{item()}</text>}
    </Index>
    ```
  </Accordion>
</AccordionGroup>
