5.0 KiB

OpenTUI Solid (@opentui/solid)

A SolidJS reconciler for building terminal user interfaces with fine-grained reactivity. Get optimal performance with Solid's signal-based approach.

Overview

OpenTUI Solid provides:

  • Custom reconciler: Solid components render to OpenTUI renderables
  • JSX intrinsics: <text>, <box>, <input>, etc.
  • Hooks: useKeyboard, useRenderer, useTimeline, etc.
  • Fine-grained reactivity: Only what changes re-renders
  • Portal & Dynamic: Advanced composition primitives

When to Use Solid

Use the Solid reconciler when:

  • You want optimal re-rendering performance
  • You prefer signal-based reactivity
  • You need fine-grained control over updates
  • Building performance-critical applications
  • You already know SolidJS

When NOT to Use Solid

Scenario Use Instead
Team knows React, not Solid @opentui/react
Maximum control needed @opentui/core
Smallest bundle size @opentui/core
Building a framework/library @opentui/core

Quick Start

bunx create-tui@latest -t solid my-app
cd my-app && bun install

The CLI creates the my-app directory for you - it must not already exist.

Options: --no-git (skip git init), --no-install (skip bun install)

Agent guidance: Always use autonomous mode with -t <template> flag. Never use interactive mode (bunx create-tui@latest my-app without -t) as it requires user prompts that agents cannot respond to.

Or manually:

bun install @opentui/solid @opentui/core solid-js
import { render } from "@opentui/solid"
import { createSignal } from "solid-js"

function App() {
  const [count, setCount] = createSignal(0)
  
  return (
    <box border padding={2}>
      <text>Count: {count()}</text>
      <box
        border
        onMouseDown={() => setCount(c => c + 1)}
      >
        <text>Click me!</text>
      </box>
    </box>
  )
}

render(() => <App />)

Core Concepts

Signals

Solid uses signals for reactive state:

import { createSignal, createEffect } from "solid-js"

function Counter() {
  const [count, setCount] = createSignal(0)
  
  // Effect runs when count changes
  createEffect(() => {
    console.log("Count is now:", count())
  })
  
  return <text>Count: {count()}</text>
}

JSX Elements

Solid maps JSX intrinsic elements to OpenTUI renderables:

// Note: Some use underscores (Solid convention)
<text>Hello</text>           // TextRenderable
<box border>Content</box>    // BoxRenderable
<input placeholder="..." />  // InputRenderable
<select options={[...]} />   // SelectRenderable
<tab_select />               // TabSelectRenderable (underscore!)
<ascii_font />               // ASCIIFontRenderable (underscore!)
<line_number />              // LineNumberRenderable (underscore!)

Text Modifiers

Inside <text>, use modifier elements:

<text>
  <strong>Bold</strong>, <em>italic</em>, and <u>underlined</u>
  <span fg="red">Colored text</span>
  <br />
  New line with <a href="https://example.com">link</a>
</text>

Available Components

Layout & Display

  • <text> - Styled text content
  • <box> - Container with borders and layout
  • <scrollbox> - Scrollable container
  • <ascii_font> - ASCII art text (note underscore)

Input

  • <input> - Single-line text input
  • <textarea> - Multi-line text input
  • <select> - List selection
  • <tab_select> - Tab-based selection (note underscore)

Code & Diff

  • <code> - Syntax-highlighted code
  • <line_number> - Code with line numbers (note underscore)
  • <diff> - Unified or split diff viewer

Text Modifiers (inside <text>)

  • <span> - Inline styled text
  • <strong>, <b> - Bold
  • <em>, <i> - Italic
  • <u> - Underline
  • <br> - Line break
  • <a> - Link

Special Components

Portal

Render children to a different mount node:

import { Portal } from "@opentui/solid"

function Overlay() {
  return (
    <Portal mount={renderer.root}>
      <box position="absolute" left={10} top={5} border>
        <text>Overlay content</text>
      </box>
    </Portal>
  )
}

Dynamic

Render components dynamically:

import { Dynamic } from "@opentui/solid"

function DynamicInput(props: { multiline: boolean }) {
  return (
    <Dynamic
      component={props.multiline ? "textarea" : "input"}
      placeholder="Enter text..."
    />
  )
}

In This Reference

  • Configuration - Project setup, tsconfig, bunfig, building
  • API - Components, hooks, render function
  • Patterns - Signals, stores, control flow, composition
  • Gotchas - Common issues, debugging, limitations

See Also

  • Core - Underlying imperative API
  • React - Alternative declarative approach
  • Components - Component reference by category
  • Layout - Flexbox layout system
  • Keyboard - Input handling and shortcuts
  • Testing - Test renderer and snapshots