# Technical Documentation: anomalyco/opentui

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/anomalyco/opentui)

### Overview (`README.md`)

<p align="center">
  <a href="https://opentui.com">
    <img alt="OpenTUI logo" src="https://opentui.com/opentui-logo-auto.svg" width="270" />
  </a>
</p>

<div align="center">
    <a href="https://www.npmjs.com/package/@opentui/core"><img alt="npm version" src="https://img.shields.io/npm/v/@opentui/core?style=flat-square" /></a>
    <a href="https://github.com/anomalyco/opentui/actions/workflows/build-core.yml"><img alt="Core build status" src="https://img.shields.io/github/actions/workflow/status/anomalyco/opentui/build-core.yml?style=flat-square&branch=main" /></a>
</div>

OpenTUI is a library to build terminal user interfaces.

- It is written in Zig.
- You write TypeScript directly or through React and Solid.
- You arrange boxes and text with flexbox.
- You add selects, inputs, and scroll boxes with keyboard and mouse controls.
- You can play sounds, show images, and render 3D graphics.

OpenCode uses OpenTUI in production for millions of users.

[Website](https://opentui.com) | [Documentation](https://opentui.com/docs) | [Packages](https://opentui.com/packages)

## Packages

- [`@opentui/core`](packages/core) - Native terminal UI library with TypeScript bindings.
- [`@opentui/react`](packages/react) - React renderer for building terminal user interfaces with OpenTUI Core.
- [`@opentui/solid`](packages/solid) - Solid renderer for building terminal user interfaces with OpenTUI Core.
- [`@opentui/keymap`](packages/keymap) - Key binding and command routing library for terminal and browser applications.
- [`@opentui/qrcode`](packages/qrcode) - QR code encoder and renderable with React and Solid surfaces.
- [`@opentui/three`](packages/three) - Three.js WebGPU renderer for OpenTUI.
- [`@opentui/ssh`](packages/ssh) - SSH server integration for OpenTUI applications.

**Private packages**

- [`@opentui/native`](packages/native) - Private workspace with the Zig implementation, native tests, and benchmarks.
- [`@opentui/examples`](packages/examples) - Private workspace with example applications and a standalone examples executable.
- [`@opentui/web`](packages/web) - Private workspace with the documentation website and the AI agent skill.

## AI agent skill

Install the OpenTUI documentation as a skill for your AI coding assistant with [`npx skills`](https://skills.sh):

```bash
npx skills add https://opentui.com --skill opentui
```

Add `-g` to install the skill globally. The skill matches the latest release, and `npx skills update` moves it to each
new release. Install from `https://opentui.com/docs/next` to follow the main branch. A skill installed with
`npx skills add anomalyco/opentui` follows the main branch; run the command above to switch it to releases.

## Development

Development requires Bun 1.4.1 or later and Zig 0.16.0.

See [AGENTS.md](AGENTS.md) for engineering, tooling, and verification conventions.

```bash
bun install
```

Run package scripts from their package directory. Run `bun run build` from the repository root after you change
native code or cross-package build outputs. Ordinary TypeScript changes do not require a root build.

Run repository tests and static checks from the repository root:

```bash
bun run test
bun run fmt:check
bun run lint
```

To link the source packages into a Bun-managed project, install that project's dependencies, then run
`./scripts/link-opentui-dev.sh /path/to/project` from the repository root. Add `--react` or `--solid` when needed.

## Contributing

OpenTUI is open for contributions.

## License

OpenTUI is licensed under the [MIT License](LICENSE).

---

### Overview (`api/README.md`)

# API history

Each `<version>.txt` file records how the API of the published `@opentui/*` packages and of the C ABI in
`packages/native/include/opentui.h` changed in that release. The documentation site reads these files to show when
each feature was added, changed, deprecated, or removed.

A file exists for every stable release from the oldest file on. A release without API changes has a file with only
the header.

## Format

```
base 0.5.14
- @opentui/core: method DiffRenderable.destroyRecursively(): void
+ @opentui/core: protected method DiffRenderable.destroySelf(): void
- @opentui/solid: method SlotRenderable.getSlotChild(parent: BaseRenderable): TextSlotRenderable | LayoutSlotRenderable
+ @opentui/solid: method SlotRenderable.getSlotChild(parent: BaseRenderable): LayoutSlotRenderable | TextSlotRenderable
```

The first line names the release that the file is relative to: `base <version>`. Usually the base is the previous
release. A patch of an older line names the release it was made from. The oldest file is a snapshot: `base none`,
and every feature of its release as a `+` line.

Each other line adds (`+`) or removes (`-`) one feature. A changed signature is a `-` line and a `+` line with the
same key. The API of a release is the API of its base, without the `-` lines, with the `+` lines.

Lines are sorted by key, then `-` before `+`, then by text. A key is `<module>: <kind> <name>`.

## Features

```
<module>: [<modifier> ...]<kind> <name><signature>
```

- `<module>` is the npm specifier of an entry point, such as `@opentui/core` or `@opentui/core/testing`. Each subpath
  of a package's `exports` map with a `types` target is an entry point. A feature exported by two entry points has a
  line for each. The module of the C ABI is `opentui.h`.
- `<modifier>` is `deprecated` (JSDoc `@deprecated`), `static`, `protected`, `abstract`, or `readonly`, in that order.
- `<kind>` is `class`, `interface`, `type`, `function`, `const`, `let`, `enum`, `enum-member`, `namespace`,
  `constructor`, `method`, `property`, `call`, `construct`, `index`, or `reexport`. The C ABI also uses `struct` and
  `field`.
- `<name>` is the export name. A member name includes its owner: `CliRenderer.suspend`, `Yoga.Node.create`. A
  constructor, call, construct, or index signature uses the owner's name. A default export is `default`.
- `<signature>` is the declaration syntax after the name, from the published `.d.ts` files. Comments are removed,
  whitespace is collapsed, and `import("…").X` is written as `X`.

Members are the own members of a class or interface. Inherited members are not repeated; the `extends` clause is on
the class line. Private, `#private`, and `@internal` members are left out. So are public members whose name starts
with `_`. Protected members, including `_` names, are API because users subclass renderables. Getters and setters
are properties; a getter without a setter is `readonly`. Each overload has its own line.

An `export * as X` of a module in the package records `namespace X` and its members as `X.member`. An export from
another package records only the export: `namespace THREE from "three"`, `reexport createElement from "react"`, or
`reexport * from "react"`. A renamed export from another package ends with `as <original name>`.

## The C ABI

```
opentui.h: function ot_scene_move_node(ot_context *, const ot_handle *node, const ot_handle *parent, uint32_t index): ot_status
opentui.h: type ot_status = int32_t
opentui.h: type ot_scene_measure_callback = void (*)(uint64_t context_id, uint32_t slot, …, float *out_size)
opentui.h: type ot_context = struct ot_context
opentui.h: struct ot_handle
opentui.h: field ot_handle.slot: uint32_t, offset 8
opentui.h: const OT_INVALID_ARGUMENT = -1
```

The lines of `opentui.h` record each declaration whose name starts with `ot_` or `OT_`:

- A function is `function`, with its parameters as the header declares them and its return type after a colon.
- A typedef of a function pointer, an integer, or an incomplete struct is `type`, with the C type after `=`.
- A struct is `struct`, and each of its fields is `field <struct>.<field>: <type>, offset <bytes>`. A field that moves
  is a change, so a line shows a layout change even when the field keeps its name and type.
- An object-like macro is `const`, with its integer value.

Types keep the header's spelling, with whitespace collapsed. The field offsets and macro values come from
`packages/core/src/native-abi.generated.ts`, which `bun run check:abi` in `packages/core` derives from the header with
the C compiler.

A release's C ABI is its commit's header: the C ABI is used from a source checkout, not from an npm package.

## Commands

Run these from the repository root.

- `bun packages/web/scripts/api.ts release <version>` writes `api/<version>.txt`: the difference between the latest
  file's API and the API of the source tree. `scripts/release.ts` runs it in the release commit. It does not
  overwrite a file with different contents. Pass `--base <version>` for a patch of an older line.
- `bun packages/web/scripts/api.ts verify <version>` compares the API that the files record for a release with its
  packages on npm. The release workflow runs it after npm serves the release. The C ABI is not on npm, so it is left
  out.
- `bun packages/web/scripts/api.ts diff` prints the file that a release of the source tree would add.
- `bun packages/web/scripts/api.ts current` prints every feature of the source tree.
- `bun packages/web/scripts/api.ts check` validates the files.
- `bun packages/web/scripts/api.ts backfill [--from <version>] [--to <version>]` writes the files of published
  releases from their npm tarballs and the header at their tags, from the oldest file on. It rewrites only files that
  changed. Run it after a change to the extractor. A release without a tag has no C ABI.
- `bun packages/web/scripts/api.ts squash <version>` starts the history at a release: its file becomes a snapshot,
  and the files of older releases go. The API that the files record for the kept releases stays the same; the site
  stops listing the older releases and the versions that added their features. Run it when nobody uses the older
  lines.

The source commands run `tsc` declaration emit for each package into a temporary directory, with the exports map that
the package's `scripts/build.ts` publishes. They do not run the package builds. The extractor reads those
declarations in the same way as a tarball, so a source tree at a release tag gives the API of that release. They read
the source tree's header for the C ABI.

---

### Overview (`packages/core/README.md`)

<p align="center">
  <a href="https://opentui.com">
    <img alt="OpenTUI logo" src="https://opentui.com/opentui-logo-auto.svg" width="270" />
  </a>
</p>

<div align="center">
    <a href="https://www.npmjs.com/package/@opentui/core"><img alt="npm version" src="https://img.shields.io/npm/v/@opentui/core?style=flat-square" /></a>
    <a href="https://github.com/anomalyco/opentui/actions/workflows/build-core.yml"><img alt="Core build status" src="https://img.shields.io/github/actions/workflow/status/anomalyco/opentui/build-core.yml?style=flat-square&branch=main" /></a>
</div>

OpenTUI is a library to build terminal user interfaces.

- It is written in Zig.
- You write TypeScript directly or through React and Solid.
- You arrange boxes and text with flexbox.
- You add selects, inputs, and scroll boxes with keyboard and mouse controls.
- You can play sounds, show images, and render 3D graphics.

OpenCode uses OpenTUI in production for millions of users.

[Website](https://opentui.com) | [Documentation](https://opentui.com/docs) | [Packages](https://opentui.com/packages)

## OpenTUI Core

`@opentui/core` supplies the renderer and TypeScript API. You use imperative renderables and events directly. The
TypeScript packages call native Zig through an internal foreign function interface (FFI) boundary.

```typescript
import { TextRenderable, createCliRenderer } from "@opentui/core"

const renderer = await createCliRenderer({ exitOnCtrlC: true })

renderer.root.add(new TextRenderable(renderer, { content: "Hello, OpenTUI!" }))
```

`exitOnCtrlC: true` calls `renderer.destroy()` when the user presses Ctrl+C. The code that creates the renderer must
call `renderer.destroy()` on every other shutdown path.

Install the package:

```bash
bun add @opentui/core
```

Then build your first app with the [quickstart](https://opentui.com/docs/getting-started/quickstart).

## Runtime and platform support

`@opentui/core` runs on Bun 1.3.14 or later, or on Node.js 26.4.0 or later with ECMAScript modules (ESM) and
`--experimental-ffi`.

Use Bun 1.4.0 or later on native Windows arm64.

The native ABI and the generated platform packages, such as `@opentui/core-linux-x64`, are internal distribution
surfaces, not application APIs.

## AI agent skill

Install the OpenTUI documentation as a skill for your AI coding assistant with [`npx skills`](https://skills.sh):

```bash
npx skills add https://opentui.com --skill opentui
```

Add `-g` to install the skill globally. The skill matches the latest release, and `npx skills update` moves it to each
new release. Install from `https://opentui.com/docs/next` to follow the main branch. A skill installed with
`npx skills add anomalyco/opentui` follows the main branch; run the command above to switch it to releases.

## License

OpenTUI is licensed under the [MIT License](https://github.com/anomalyco/opentui/blob/main/LICENSE).

---

### Overview (`packages/keymap/README.md`)

# @opentui/keymap

A host-agnostic keymap engine for terminal and DOM apps. Same core, multiple adapters.

It models keybindings as priority-ordered, focus-scoped layers attached to targets (terminal renderables or DOM elements). The core is intentionally bare; everything beyond raw key dispatch is opt-in via addons, parsers, expanders, resolvers, and field compilers.

## Highlights

- **Host-agnostic core** over a small `KeymapHost` interface with host metadata, focus, parent traversal, target destruction, key press/release, optional raw input, and synthetic command events.
- **Target-aware focus routing** for global layers plus local `focus` / `focus-within` layers that follow the active target's parent chain.
- **Layered bindings** with priority ordering, newest-first ties, `fallthrough`, and `preventDefault` control.
- **Branch-aware multi-key sequences** over flat compiled bindings, with a public pending-sequence API, synchronous `pendingSequence` events, active continuation queries, and automatic invalidation on focus changes.
- **Programmable exact-vs-prefix disambiguation** (e.g. `g` vs `gg`) with `runExact`, `continueSequence`, `clear`, and deferred `AbortSignal` + `sleep` decisions. Ships a Neovim-style timeout resolver.
- **Pluggable binding language**: stackable binding parsers, key expanders, layer-binding transformers, binding transformers, command resolvers, command transformers, and ordered event-match resolvers.
- **Extensible schema and activation**: register custom fields on layers, bindings, and commands. Field compilers can emit `attrs`; all field kinds can gate activation via `require(...)` and `activeWhen(matcher)`.
- **Reactive matchers** with subscription-driven state notifications, plus React store and Solid signal helpers.
- **Raw and key intercepts** before and after normal binding dispatch, including pre-binding `consume({ preventDefault, stopPropagation })`, post-dispatch handled/no-match outcomes, and raw input `stop()` handling.
- **Command catalog and dispatch** with named commands, inline command handlers, command chains, namespaces, search, visibility tiers (`registered` / `reachable` / `active`), binding queries, `runCommand`, and focus-aware `dispatchCommand`.
- **Opt-in graph snapshots and diagnostics** for layers, commands, bindings, sequence nodes, pending paths, inactive reasons, shadowing, stable warning/error codes, and lint-style layer analyzers.
- **Broad key coverage** in the default parser, including function keys, navigation/editing keys, numpad keys, media keys, left/right modifiers, `super`, `hyper`, and literal `+` bindings.
- **Platform-aware modifier aliases** via `registerModBindings`, resolving `mod+...` from host metadata while preserving display strings.

## Addons

`@opentui/keymap/addons` ships ready-made building blocks:

- `registerDefaultKeys` — `ctrl+shift+s` style parser and event matching.
- `registerLeader`, `registerTimedLeader` — leader tokens with optional timeout.
- `registerEmacsBindings` — `ctrl+x ctrl+s` chords.
- `registerExCommands` — `:write`-style commands with `aliases` and `nargs`.
- `registerCommaBindings`, `registerModBindings`, `registerAliasesField`, `registerBindingOverrides`.
- `registerEnabledFields`, `registerMetadataFields` (`desc`, `group`, `title`, `category`).
- `registerNeovimDisambiguation`, `registerEscapeClearsPendingSequence`, `registerBackspacePopsPendingSequence`.
- `registerDeadBindingWarnings`, `registerUnresolvedCommandWarnings`.

`@opentui/keymap/addons/opentui` adds OpenTUI-specific pieces: base-layout matching via `event.baseCode` for non-Latin keys and the digit row (the same rule as Core), and pre-wired textarea / edit-buffer commands. Direct event matches win before fallback matches, even across layers.

## Entry Points

- `@opentui/keymap` — core API
- `@opentui/keymap/addons` — universal addons
- `@opentui/keymap/addons/opentui` — universal + OpenTUI addons
- `@opentui/keymap/extras/graph` — graph snapshot helpers for debug and graph UIs
- `@opentui/keymap/testing` — host-agnostic fake keymap host and diagnostics for addon tests
- `@opentui/keymap/html` — HTML adapter
- `@opentui/keymap/opentui` — OpenTUI adapter
- `@opentui/keymap/react` — `KeymapProvider`, `useKeymap`, `useBindings`, `useActiveKeys`, `usePendingSequence`, `reactiveMatcherFromStore`
- `@opentui/keymap/solid` — `KeymapProvider`, `useKeymap`, `useKeymapSelector`, `useBindings`, `reactiveMatcherFromSignal`
- `@opentui/keymap/extras` — helpers for cheat-sheet UIs (`createBindingLookup`, `commandBindings`, `formatCommandBindings`)

## Usage

```tsx
import { registerDefaultKeys } from "@opentui/keymap/addons"
import { createOpenTuiKeymap } from "@opentui/keymap/opentui"
import { KeymapProvider } from "@opentui/keymap/react"

const keymap = createOpenTuiKeymap(renderer)
registerDefaultKeys(keymap)

createRoot(renderer).render(
  <KeymapProvider keymap={keymap}>
    <App />
  </KeymapProvider>,
)
```

Create a keymap, install the addons you want, then pass the configured instance to your app. The React and Solid entrypoints consume a pre-created OpenTUI keymap through context.

`Keymap` is the core runtime. Pass feature factories to its constructor when you need graph snapshots or layer analyzers.

## Adapters

Adapters implement a small `KeymapHost` interface (`metadata`, `rootTarget`, `getFocusedTarget`, `getParentTarget`, `onKeyPress`, `onKeyRelease`, `onFocusChange`, `onTargetDestroy`, ...). The HTML adapter normalizes DOM key names (`Escape` → `escape`, `ArrowUp` → `up`, `Meta` → `super`, `Alt` → `meta`) and tracks targets via `MutationObserver`. The OpenTUI adapter hooks `CliRenderer` `keypress`, `keyrelease`, focus, destroy, target destroy, and raw input events.

## Formatting Keys

Use `keymap.formatKey` when displaying raw binding strings. It runs them through the keymap's parsers and tokens before stringifying.

```ts
keymap.formatKey("<leader>s", { separator: " " }) // "space s"
keymap.formatKey("<leader>s", { preferDisplay: true }) // "<leader>s"
```

## Re-entry

Runtime/data re-entry is supported during dispatch: command handlers, intercepts, and pending-sequence listeners may read or write runtime data and pending-sequence state.

Structural re-entry is **not** supported. Do not register or unregister layers, tokens, parsers, or resolvers while a dispatch is in flight.

## Installation

```bash
bun install @opentui/keymap
```

## Development

```bash
bun run build
bun run test
bun src/keymap-benchmark.ts
```

The HTML demo lives in the docs app at `/demos/keymap-html/` under `packages/web`.

---

### Overview (`packages/native/README.md`)

# Native API

OpenTUI uses Context-owned scenes and Sessions for production rendering, including
text, editors, custom paint hooks, images, detached surfaces, and split output.
Standalone resources use the same checked ownership model without a terminal.

Read [Resources and ownership](https://opentui.com/docs/native/resources) for
resource bindings, text copy units, and scoped framebuffer access.
[Frames and output](https://opentui.com/docs/native/frames) defines mutation
admission, painted drafts, and transport completion.
[How Core uses native](https://opentui.com/docs/native/core) explains TypeScript
staging and resource ownership.
[Host I/O and time](https://opentui.com/docs/native/host-io-time) defines Context
file operations, Session deadlines, and native diagnostic clocks.

## API surfaces

- [`src/opentui.zig`](src/opentui.zig) is the public Zig module. It exports the
  checked `Context` API and raw Zig primitives, including `CliRenderer`,
  `NativeRenderable`, `OptimizedBuffer`, text buffers, and pools. Callers manage
  the lifetimes of raw primitives. Importing a raw primitive does not add Context
  checks to it.
- [`include/opentui.h`](include/opentui.h) defines the checked `ot_*` C ABI for
  Contexts, Sessions, scenes, drawing, text, editors, styles, leases, clipboard,
  and diagnostics.
  The ABI is experimental and at version 1. Core renders through it.
- [`../core/src/zig.ts`](../core/src/zig.ts) supplies TypeScript wrappers over that
  checked ABI. Its checked signatures, callbacks, constants, and record layouts come
  from [`native-abi.generated.ts`](../core/src/native-abi.generated.ts).

## ABI generation and builds

Use C headers and libraries from the same revision. Set each versioned record's
exact `struct_size` and `abi_version`, and leave unused flags and reserved fields
zero. Follow the output and failure contract of each operation in the header.

From `packages/core`:

```sh
bun run generate:abi
bun run check:abi
bun run test:abi
```

[`scripts/native-abi.ts`](../core/scripts/native-abi.ts) uses Zig Translate-C and
[`scripts/native-abi.zig`](../core/scripts/native-abi.zig) reflection to derive scalar
widths, signatures, callback types, constants, record sizes, alignment, and field
offsets from the header. Pointer nullability, retention, address fields, and portable
`buffer`/`ptr` policy live in
[`scripts/native-abi-pointers.ts`](../core/scripts/native-abi-pointers.ts), because C
types cannot prove lifetimes. Review that metadata when ownership contracts change.
Do not edit generated bindings. `check:abi` detects stale output. Use
`bun run check:abi --all-targets` to also compare the layouts of all supported targets.
An unsupported record shape or calling convention fails the script. It does not
produce partial metadata. C compiler assertions also check complete function and
callback prototypes, record layouts, field types, and constant values. Translate-C
alone is not enough, because it can drop callback calling-convention attributes and
ignore `#pragma pack`.

From `packages/native`, `bun run build` installs the header and libraries under
`lib/<target>/` for the host target. Linux and macOS produce `libopentui.a` beside
the shared library. Windows produces `opentui-static.lib`, `opentui.lib` for DLL
imports, and `opentui.dll`. Static linkage also needs the platform and C++ runtime
libraries. `zig build -Dall` builds all supported targets.
`zig build -Dlibrary-target=<target>` builds one target.

```sh
zig build test-abi --summary all
```

This command runs the C fixture against the static and shared libraries on the host.
On a glibc Linux host, the fixture targets glibc 2.17. `check:abi --all-targets`
checks layouts for all eight supported targets. It does not test macOS or Windows
runtime linkage or terminal behavior.

The external [`examples/hello`](examples/hello) package imports the public Zig module
without JavaScript.

---

### Overview (`packages/qrcode/README.md`)

# @opentui/qrcode

QR code encoder and renderable for OpenTUI.

```ts
import { createCliRenderer } from "@opentui/core"
import { QRCodeRenderable } from "@opentui/qrcode"

const renderer = await createCliRenderer()
renderer.root.add(
  new QRCodeRenderable(renderer, {
    content: "https://opentui.com",
    quietZone: 4,
    scale: 2,
  }),
)
```

React JSX support is registered explicitly:

```tsx
import { registerQRCode } from "@opentui/qrcode/react"

registerQRCode()
```

Solid JSX support is registered explicitly:

```tsx
import { registerQRCode } from "@opentui/qrcode/solid"

registerQRCode()
```

---

### Overview (`packages/react/README.md`)

# @opentui/react

A React renderer for [OpenTUI Core](https://github.com/anomalyco/opentui). Build terminal user interfaces with React components, hooks, and state.

## Installation

Quick start with [bun](https://bun.sh) and [create-tui](https://github.com/msmps/create-tui):

```bash
bun create tui --template react
```

Manual installation:

```bash
bun install @opentui/react @opentui/core react
```

## Quick Start

```tsx
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`:

```json
{
  "compilerOptions": {
    "lib": ["ESNext", "DOM"],
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "jsx": "react-jsx",
    "jsxImportSource": "@opentui/react",
    "strict": true,
    "skipLibCheck": true
  }
}
```

## Table of Contents

- [Core Concepts](#core-concepts)
  - [Components](#components)
  - [Styling](#styling)
- [API Reference](#api-reference)
  - [createRoot(renderer)](#createrootrenderer)
  - [render(element, config?)](#renderelement-config-removed)
  - [Hooks](#hooks)
    - [useRenderer()](#userenderer)
    - [useKeyboard(handler, options?)](#usekeyboardhandler-options)
    - [usePaste(handler)](#usepastehandler)
    - [useFocus(handler)](#usefocushandler)
    - [useBlur(handler)](#useblurhandler)
    - [useSelectionHandler(handler)](#useselectionhandlerhandler)
    - [useOnResize(callback)](#useonresizecallback)
    - [useTerminalDimensions()](#useterminaldimensions)
    - [useTimeline(options?)](#usetimelineoptions)
- [Components](#components-1)
  - [Layout & Display Components](#layout--display-components)
    - [Text Component](#text-component)
    - [Box Component](#box-component)
    - [Scrollbox Component](#scrollbox-component)
    - [ASCII Font Component](#ascii-font-component)
  - [Input Components](#input-components)
    - [Input Component](#input-component)
    - [Textarea Component](#textarea-component)
    - [Select Component](#select-component)
  - [Code & Diff Components](#code--diff-components)
    - [Code Component](#code-component)
    - [Line Number Component](#line-number-component)
    - [Diff Component](#diff-component)
- [Examples](#examples)
  - [Login Form](#login-form)
  - [Counter with Timer](#counter-with-timer)
  - [System Monitor Animation](#system-monitor-animation)
  - [Styled Text Showcase](#styled-text-showcase)
- [Component Extension](#component-extension)
- [Using React DevTools](#using-react-devtools)

## Core Concepts

### Components

OpenTUI React includes these built-in components. Each component maps to an OpenTUI Core renderable:

**Layout & Display:**

- **`<text>`** - Display text with styling
- **`<box>`** - Container with borders and layout
- **`<scrollbox>`** - A scrollable box
- **`<ascii-font>`** - Display ASCII art text with different font styles

For QR codes, call `registerQRCode()` from `@opentui/qrcode/react`. It registers the `<qr-code>` element.

**Input Components:**

- **`<input>`** - Text input field
- **`<textarea>`** - Multi-line text input field
- **`<select>`** - Vertical list of options
- **`<tab-select>`** - Tab-based selection

**Code & Diff Components:**

- **`<code>`** - Code block with syntax highlighting
- **`<line-number>`** - Code display with line numbers, diff highlights, and diagnostics
- **`<diff>`** - Unified or split diff viewer with syntax highlighting

**Helpers:**

- **`<span>`, `<strong>`, `<em>`, `<u>`, `<b>`, `<i>`, `<br>`, `<a>`** - Text modifiers (_use them only inside a `<text>` component_)

### Styling

Style a component with props or with the `style` prop:

```tsx
// Direct props
<box backgroundColor="blue" padding={2}>
  <text>Hello, world!</text>
</box>

// Style prop
<box style={{ backgroundColor: "blue", padding: 2 }}>
  <text>Hello, world!</text>
</box>
```

## API Reference

### `createRoot(renderer)`

Creates a root for rendering a React tree with the given CLI renderer.

```tsx
import { createCliRenderer } from "@opentui/core"
import { createRoot } from "@opentui/react"

const renderer = await createCliRenderer({
  // Optional renderer configuration
  exitOnCtrlC: false,
})
createRoot(renderer).render(<App />)
```

**Parameters:**

- `renderer`: A `CliRenderer` instance (typically created with `createCliRenderer()`)

**Returns:** A root with a `render(node)` method and an `unmount()` method. `render(node)` accepts a React element.
`unmount()` removes the React tree and keeps the renderer active. `renderer.destroy()` also unmounts the root.

### `render(element, config?)` (Removed)

> **Removed:** `@opentui/react` does not export `render()`. Use `createRoot(renderer).render(node)` instead.

### Hooks

#### `useRenderer()`

Access the OpenTUI renderer instance.

```tsx
import { useRenderer } from "@opentui/react"
import { useEffect } from "react"

function App() {
  const renderer = useRenderer()

  useEffect(() => {
    renderer.console.show()
    console.log("Hello, from the console!")
  }, [])

  return <box />
}
```

#### `useKeyboard(handler, options?)`

Handle keyboard events.

```tsx
import { useKeyboard } from "@opentui/react"

function App() {
  useKeyboard((key) => {
    if (key.name === "escape") {
      process.exit(0)
    }
  })

  return <text>Press ESC to exit</text>
}
```

**Parameters:**

- `handler`: Callback function that receives a `KeyEvent` object
- `options?`: Optional configuration object:
  - `release?`: Boolean to include key release events (default: `false`)

By default, the handler gets press events only. Press events include key repeats with `repeated: true`. Set `options.release` to `true` to also get release events.

**Example with release events:**

```tsx
import { useKeyboard } from "@opentui/react"
import { useState } from "react"

function App() {
  const [pressedKeys, setPressedKeys] = useState<Set<string>>(new Set())

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

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

#### `usePaste(handler)`

Handle terminal paste events (bracketed paste).

```tsx
import { decodePasteBytes } from "@opentui/core"
import { usePaste } from "@opentui/react"

function App() {
  usePaste((event) => {
    const text = decodePasteBytes(event.bytes)
    console.log("Pasted text:", text)
  })

  return <text>Paste something into the terminal</text>
}
```

**Parameters:**

- `handler`: Callback function that receives a `PasteEvent` object with `bytes: Uint8Array` (decode with `decodePasteBytes` from `@opentui/core`)

#### `useFocus(handler)`

Subscribe to terminal window focus events. The handler runs when the terminal window gains focus.

```tsx
import { useFocus } from "@opentui/react"

function App() {
  useFocus(() => {
    console.log("Terminal gained focus")
  })

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

**Parameters:**

- `handler`: Callback function invoked when the terminal gains focus

#### `useBlur(handler)`

Subscribe to terminal window blur events. The handler runs when the terminal window loses focus.

```tsx
import { useBlur } from "@opentui/react"

function App() {
  useBlur(() => {
    console.log("Terminal lost focus")
  })

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

**Parameters:**

- `handler`: Callback function invoked when the terminal loses focus

#### `useSelectionHandler(handler)`

Handle text selection events, such as a mouse drag selection.

```tsx
import { useSelectionHandler } from "@opentui/react"

function App() {
  useSelectionHandler((selection) => {
    const text = selection.getSelectedText()
    console.log("Selected:", text)
  })

  return <text selectable>Select this text with your mouse</text>
}
```

**Parameters:**

- `handler`: Callback function that receives a `Selection` object with methods like `getSelectedText()`

#### `useOnResize(callback)`

Handle terminal resize events.

```tsx
import { useOnResize, useRenderer } from "@opentui/react"
import { useEffect } from "react"

function App() {
  const renderer = useRenderer()

  useEffect(() => {
    renderer.console.show()
  }, [renderer])

  useOnResize((width, height) => {
    console.log(`Terminal resized to ${width}x${height}`)
  })

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

#### `useTerminalDimensions()`

Get the current terminal dimensions. The hook updates them when the terminal resizes.

```tsx
import { useTerminalDimensions } from "@opentui/react"

function App() {
  const { width, height } = useTerminalDimensions()

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

**Returns:** An object with `width` and `height` properties representing the current terminal dimensions.

#### `useTimeline(options?)`

Create one `Timeline` for animations. The hook registers the timeline with the animation engine after mount. It pauses and unregisters the timeline during React effect cleanup.

```tsx
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" }} />
}
```

**Parameters:**

- `options?`: Optional `TimelineOptions` object with properties:
  - `duration?`: Animation duration in milliseconds (default: 1000)
  - `loop?`: Whether the timeline should loop (default: false)
  - `autoplay?`: Whether to automatically start the timeline (default: true)
  - `onComplete?`: Callback when the timeline completes
  - `onPause?`: Callback when the timeline pauses

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

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

## Components

### Layout & Display Components

#### Text Component

Display text with rich formatting.

```tsx
function App() {
  return (
    <box>
      {/* Simple text */}
      <text>Hello World</text>

      {/* Rich text with children */}
      <text>
        <span fg="red">Red Text</span>
      </text>

      {/* Text modifiers */}
      <text>
        <strong>Bold</strong>, <em>Italic</em>, and <u>Underlined</u>
      </text>
    </box>
  )
}
```

#### Box Component

Container with borders and layout capabilities.

```tsx
function App() {
  return (
    <box flexDirection="column">
      {/* Basic box */}
      <box border>
        <text>Simple box</text>
      </box>

      {/* Box with title and styling */}
      <box title="Settings" titleColor="yellow" border borderStyle="double" padding={2} backgroundColor="blue">
        <text>Box content</text>
      </box>

      {/* Styled box */}
      <box
        style={{
          border: true,
          width: 40,
          height: 10,
          margin: 1,
          alignItems: "center",
          justifyContent: "center",
        }}
      >
        <text>Centered content</text>
      </box>
    </box>
  )
}
```

#### Scrollbox Component

A scrollable box.

```tsx
function App() {
  return (
    <scrollbox
      style={{
        rootOptions: {
          backgroundColor: "#24283b",
        },
        wrapperOptions: {
          backgroundColor: "#1f2335",
        },
        viewportOptions: {
          backgroundColor: "#1a1b26",
        },
        contentOptions: {
          backgroundColor: "#16161e",
        },
        scrollbarOptions: {
          showArrows: true,
          trackOptions: {
            foregroundColor: "#7aa2f7",
            backgroundColor: "#414868",
          },
        },
      }}
      focused
    >
      {Array.from({ length: 1000 }).map((_, i) => (
        <box
          key={i}
          style={{ width: "100%", padding: 1, marginBottom: 1, backgroundColor: i % 2 === 0 ? "#292e42" : "#2f3449" }}
        >
          <text content={`Box ${i}`} />
        </box>
      ))}
    </scrollbox>
  )
}
```

#### ASCII Font Component

Display ASCII art text with different font styles.

```tsx
import { useState } from "react"

function App() {
  const text = "ASCII"
  const [font, setFont] = useState<"block" | "shade" | "slick" | "tiny">("tiny")

  return (
    <box style={{ border: true, paddingLeft: 1, paddingRight: 1 }}>
      <box
        style={{
          height: 8,
          border: true,
          marginBottom: 1,
        }}
      >
        <select
          focused
          onChange={(_, option) => setFont(option?.value)}
          showScrollIndicator
          options={[
            {
              name: "Tiny",
              description: "Tiny font",
              value: "tiny",
            },
            {
              name: "Block",
              description: "Block font",
              value: "block",
            },
            {
              name: "Slick",
              description: "Slick font",
              value: "slick",
            },
            {
              name: "Shade",
              description: "Shade font",
              value: "shade",
            },
          ]}
          style={{ flexGrow: 1 }}
        />
      </box>

      <ascii-font text={text} font={font} />
    </box>
  )
}
```

### Input Components

#### Input Component

Text input field with event handling.

```tsx
import { useState } from "react"

function App() {
  const [value, setValue] = useState("")

  return (
    <box title="Enter your name" style={{ border: true, height: 3 }}>
      <input
        placeholder="Type here..."
        focused
        onInput={setValue}
        onSubmit={(value) => console.log("Submitted:", value)}
      />
    </box>
  )
}
```

#### Textarea Component

```tsx
import type { TextareaRenderable } from "@opentui/core"
import { useKeyboard, useRenderer } from "@opentui/react"
import { useEffect, useRef } from "react"

function App() {
  const renderer = useRenderer()
  const textareaRef = useRef<TextareaRenderable>(null)

  useEffect(() => {
    renderer.console.show()
  }, [renderer])

  useKeyboard((key) => {
    if (key.name === "return") {
      console.log(textareaRef.current?.plainText)
    }
  })

  return (
    <box title="Interactive Editor" style={{ border: true, flexGrow: 1 }}>
      <textarea ref={textareaRef} placeholder="Type here..." focused />
    </box>
  )
}
```

#### Select Component

Choose one option from a vertical list.

```tsx
import type { SelectOption } from "@opentui/core"
import { useState } from "react"

function App() {
  const [selectedIndex, setSelectedIndex] = useState(0)

  const options: SelectOption[] = [
    { name: "Option 1", description: "Option 1 description", value: "opt1" },
    { name: "Option 2", description: "Option 2 description", value: "opt2" },
    { name: "Option 3", description: "Option 3 description", value: "opt3" },
  ]

  return (
    <box style={{ border: true, height: 24 }}>
      <select
        style={{ height: 22 }}
        options={options}
        focused={true}
        onChange={(index, option) => {
          setSelectedIndex(index)
          console.log("Selected:", option)
        }}
      />
    </box>
  )
}
```

### Code & Diff Components

#### Code Component

Create a theme once for its renderer, not during a component render. Release it after you destroy its consumers.

```tsx
import { createCliRenderer, RGBA, SyntaxStyle } from "@opentui/core"
import { createRoot } from "@opentui/react"

const renderer = await createCliRenderer()
const syntaxStyle = SyntaxStyle.fromStyles(
  {
    keyword: { fg: RGBA.fromHex("#ff6b6b"), bold: true }, // red, bold
    string: { fg: RGBA.fromHex("#51cf66") }, // green
    comment: { fg: RGBA.fromHex("#868e96"), italic: true }, // gray, italic
    number: { fg: RGBA.fromHex("#ffd43b") }, // yellow
    default: { fg: RGBA.fromHex("#ffffff") }, // white
  },
  renderer.nativeScene!,
)

const codeExample = `function hello() {
  // This is a comment

  const message = "Hello, world!"
  const count = 42

  return message + " " + count
}`

function App() {
  return (
    <box style={{ border: true, flexGrow: 1 }}>
      <code content={codeExample} filetype="javascript" syntaxStyle={syntaxStyle} />
    </box>
  )
}

try {
  createRoot(renderer).render(<App />)
  await renderer.closed
} finally {
  renderer.destroy()
  syntaxStyle.destroy()
}
```

#### Line Number Component

Display code with line numbers, and optionally add diff highlights or diagnostic indicators.

```
/* Detailed source-code truncated for AI context efficiency. */
```

For a more complete example with interactive diff highlights and diagnostics, see [`examples/line-number.tsx`](examples/line-number.tsx).

#### Diff Component

Display a unified or split diff with syntax highlighting, line numbers, word wrapping, and custom colors.

For a complete interactive example with theme switching and keybindings, see [`examples/diff.tsx`](examples/diff.tsx).

## Examples

### Login Form

```
/* Detailed source-code truncated for AI context efficiency. */
```

### Counter with Timer

```tsx
import { createCliRenderer } from "@opentui/core"
import { createRoot } from "@opentui/react"
import { useEffect, useState } from "react"

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

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

    return () => clearInterval(interval)
  }, [])

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

const renderer = await createCliRenderer()
createRoot(renderer).render(<App />)
```

### System Monitor Animation

```
/* Detailed source-code truncated for AI context efficiency. */
```

### Styled Text Showcase

```tsx
import { createCliRenderer } from "@opentui/core"
import { createRoot } from "@opentui/react"

function App() {
  return (
    <>
      <text>Simple text</text>
      <text>
        <strong>Bold text</strong>
      </text>
      <text>
        <u>Underlined text</u>
      </text>
      <text>
        <span fg="red">Red text</span>
      </text>
      <text>
        <span fg="blue">Blue text</span>
      </text>
      <text>
        <strong fg="red">Bold red text</strong>
      </text>
      <text>
        <strong>Bold</strong> and <span fg="blue">blue</span> combined
      </text>
    </>
  )
}

const renderer = await createCliRenderer()
createRoot(renderer).render(<App />)
```

## Component Extension

To create a custom component, extend an OpenTUI Core renderable class and register it with `extend()`.
React calls the constructor with `{ id, ...props }`. After construction, React assigns the props to the instance again.
This example overrides the `renderSelf()` [paint hook](https://opentui.com/docs/extend/custom-renderables#use-paint-hooks). The hook uses screen cell coordinates, so it places the label
relative to `this.x` and `this.y`:

```
/* Detailed source-code truncated for AI context efficiency. */
```

## Using React DevTools

OpenTUI React supports [React DevTools](https://github.com/facebook/react/tree/master/packages/react-devtools) for debugging your terminal applications. To enable DevTools integration:

1. Install the optional peer dependency:

```bash
bun add --dev react-devtools-core@7
```

2. Start the standalone React DevTools:

```bash
npx react-devtools@7
```

3. Run your app with the `DEV` environment variable:

```bash
DEV=true bun run your-app.ts
```

After the app starts, React DevTools shows the component tree. You can inspect and change props while the app runs. The terminal UI shows each change.

### Process Exit with DevTools

When DevTools is connected, the WebSocket connection can keep your process from exiting on its own.

---

### Overview (`packages/solid/README.md`)

# @opentui/solid

Solid.js support for [OpenTUI](https://github.com/anomalyco/opentui).

## Installation

```bash
bun install solid-js @opentui/solid
```

## Usage

1. Add jsx config to tsconfig.json:

```json
{
  "compilerOptions": {
    "jsx": "preserve",
    "jsxImportSource": "@opentui/solid"
  }
}
```

2. Add preload script to bunfig.toml:

```toml
preload = ["@opentui/solid/preload"]
```

3. Add render function to index.tsx:

```tsx
import { render } from "@opentui/solid"

render(() => <text>Hello, World!</text>)
```

4. Run with `bun index.tsx`.

5. To build use [Bun.build](https://bun.com/docs/bundler) ([source](https://github.com/anomalyco/opentui/issues/122)):

```ts
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",
  },
})
```

## Table of Contents

- [Core Concepts](#core-concepts)
  - [Components](#components)
- [API Reference](#api-reference)
  - [render(node, rendererOrConfig?)](#rendernode-rendererorconfig)
  - [testRender(node, options?)](#testrendernode-options)
  - [extend(components)](#extendcomponents)
  - [getComponentCatalogue()](#getcomponentcatalogue)
  - [Hooks](#hooks)
  - [Portal](#portal)
  - [Dynamic](#dynamic)
- [Components](#components-1)
  - [Layout & Display](#layout--display)
  - [Input](#input)
  - [Code & Diff](#code--diff)
  - [Text Modifiers](#text-modifiers)

## Core Concepts

### Components

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

- **Layout & Display:** `text`, `box`, `scrollbox`, `ascii_font`
- **Input:** `input`, `textarea`, `select`, `tab_select`
- **Code & Diff:** `code`, `line_number`, `diff`
- **Text Modifiers:** `span`, `strong`, `b`, `em`, `i`, `u`, `br`, `a`

## API Reference

### `render(node, rendererOrConfig?)`

Render a Solid component tree into a CLI renderer. If you omit `rendererOrConfig`, `render()` creates a renderer with default options. `render()` returns a promise that resolves after the initial mount. Destroying the renderer disposes the Solid root.

```tsx
import { render } from "@opentui/solid"

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

**Parameters:**

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

### `testRender(node, options?)`

Create a test renderer for snapshots and interaction tests.

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

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

### `extend(components)`

Register custom renderables as JSX intrinsic elements.

```tsx
import { extend } from "@opentui/solid"

extend({ customBox: CustomBoxRenderable })
```

Solid calls the constructor with `{ id }` only. After construction, Solid applies each JSX prop to the instance. A class
that reads a value only from its constructor options does not get that value from JSX.

### `getComponentCatalogue()`

Returns the current component catalogue that powers JSX tag lookup.

### Hooks

- `useRenderer()`
- `onResize(callback)`
- `onFocus(callback)`
- `onBlur(callback)`
- `useTerminalDimensions()`
- `useKeyboard(handler, options?)`
- `usePaste(handler)`
- `useSelectionHandler(handler)`
- `useTimeline(options?)`

### `Portal`

Render children into a different mount node. Use it for overlays and tooltips.

```tsx
import { Portal } from "@opentui/solid"
;<Portal mount={renderer.root}>
  <box border>Overlay</box>
</Portal>
```

### `Dynamic`

Render arbitrary intrinsic elements or components dynamically.

```tsx
import { Dynamic } from "@opentui/solid"
;<Dynamic component={isMultiline() ? "textarea" : "input"} />
```

## Components

### Layout & Display

- `text`: styled text container
- `box`: layout container with borders, padding, and flex settings
- `scrollbox`: scrollable container
- `ascii_font`: ASCII art text renderer

For QR codes, call `registerQRCode()` from `@opentui/qrcode/solid`. It registers the `qr_code` element.

### Input

- `input`: single-line text input
- `textarea`: multi-line text input
- `select`: list selection
- `tab_select`: tab-based selection

### Code & Diff

- `code`: syntax-highlighted code blocks
- `line_number`: line-numbered code display with diff/diagnostic helpers
- `diff`: unified or split diff viewer

### Text Modifiers

Use these only inside a `text` component:

- `span`: inline styled text
- `strong`/`b`: bold text
- `em`/`i`: italic text
- `u`: underline text
- `br`: line break
- `a`: link text with `href`

---

### Overview (`packages/ssh/README.md`)

# @opentui/ssh

Serve OpenTUI apps over SSH.

`@opentui/ssh` gives each incoming SSH shell its own OpenTUI
[`CliRenderer`](../core). The renderer reads input from the SSH channel, writes
output to it, and tracks the size of the client's PTY. You choose what to render.
The package depends only on `@opentui/core`, not on `@opentui/react` or
`@opentui/solid`, so the same server works with all three.

```ts
import { createServer } from "@opentui/ssh"
import { BoxRenderable, TextRenderable } from "@opentui/core"

const server = createServer({
  hostKey: { path: "./host_key" }, // auto-generated & persisted on first run
  auth: { publicKey: "any" }, // accepts any verified key, so every client gets an identity
}).serve((session) => {
  const { renderer, identity } = session
  const box = new BoxRenderable(renderer, { width: "100%", height: "100%", border: true })
  box.add(new TextRenderable(renderer, { content: `Hello, ${identity.username}!` }))
  renderer.root.add(box)
  // the server destroys the renderer when the session closes. Use onClose only for your own cleanup.
})

await server.listen(2222)
```

```
ssh -p 2222 localhost
```

## Install

```sh
bun add @opentui/ssh
# or
npm install @opentui/ssh
```

`@opentui/core` is a peer dependency. Supported runtimes are Bun ≥ 1.3.14 and
Node.js ≥ 26.4.0. CI runs the SSH integration suite with Bun on macOS, Linux,
and Windows. CI also installs the packed ESM packages and runs a real renderer
session with Node.js and Bun. Start Node applications that create renderers
with `node --experimental-ffi server.mjs`.

Use Bun ≥ 1.4.0 on native Windows arm64.

## The shape: `createServer(config).serve(handler)`

Static setup goes in the `createServer({...})` config object. Add cross-cutting
concerns with `.use()`. **`serve(handler)` seals the chain with the per-session
handler and returns a startable server.** The handler goes on `serve()`, not in
the config. This lets the builder collect the typed `context` that each `use()`
adds and pass it to the handler. A server without a handler is a compile error,
because the builder has no `listen()` until you call `serve()`.

```ts
const server = createServer({
  // optional, all with sensible defaults:
  // auth, hostKey, idleTimeout, maxTimeout, limits, startupBanner, onError
})
  .use(logging()) // optional middleware (see "Middleware" below)
  .serve((session) => {
    /* mount your app on session.renderer — REQUIRED */
  })

await server.listen() // defaults to port 2222 on 127.0.0.1; pass (port, host) to change
```

`listen(port = 2222, host = "127.0.0.1")` returns `{ host, port, fingerprints }`.
Pass `0` for an ephemeral port. Pass a host like `"0.0.0.0"` or `"::"` to listen
on all interfaces, which is common in containers. With no auth, listening on a
host other than `localhost`, `127.0.0.1`, or `::1` logs a warning. It does not
throw, because an intentionally exposed TUI is valid.

### `Session`

The handler you pass to `serve()` receives a `Session` with the live `renderer`.
Middleware receives a `MiddlewareSession` **without** `renderer`. The server
creates the renderer only after the chain authorizes the session, so a gating
middleware that declines never creates one. Everything else is shared:

| Field           | What it is                                                                                                                                                                                 |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `renderer`      | A `CliRenderer` bound to this SSH channel, sized to the client PTY. **Handler-only.** A middleware's `MiddlewareSession` does not have it. The server destroys it when the session closes. |
| `identity`      | Who connected and how, **narrowed to your configured auth** (see below).                                                                                                                   |
| `context`       | Per-session object with the typed fields that upstream middleware added with `next({...})`. It is `{}` with no middleware.                                                                 |
| `term`          | The client's `TERM` (for example `"xterm-256color"`).                                                                                                                                      |
| `cols` / `rows` | Current terminal size. See [Native transport](#native-transport) for resize timing.                                                                                                        |
| `hasPty`        | Whether the client requested a PTY. Use it for `requirePty`-style middleware.                                                                                                              |
| `remoteAddress` | `{ address, port? }` client socket endpoint for logging, rate limiting, and policy.                                                                                                        |
| `onResize(cb)`  | Runs after the renderer accepts a new client size.                                                                                                                                         |
| `onClose(cb)`   | Runs once when the session closes for any reason. Do your own per-session cleanup here. The server destroys the renderer for you.                                                          |
| `write(data)`   | Sends raw bytes to the client without the renderer's frame diffing. Use it for terminal control that the renderer does not model (OSC 52 clipboard, window title, a bell).                 |
| `end()`         | Closes only this session.                                                                                                                                                                  |

### Native transport

Every shell uses Core's native scene and bounded Session output. Core, React, and
Solid applications use the same renderer attached to the SSH session.

```ts
createServer({}).serve((session) => {
  session.renderer.root.add(
    new TextRenderable(session.renderer, {
      content: "Native SSH",
    }),
  )
})
```

Each shell gets one Core native Session before middleware runs. Middleware
output, terminal setup, frames, raw writes, and restoration share its ordered
output budget. Core's Session driver writes to the SSH channel itself. It counts
bytes as delivered when each write callback runs, not on `drain`. SSH adds no
second output queue or wrapper stream. Core's production limits allow 8 MiB of retained output and
reserve one 64 KiB chunk of it for terminal control and restoration. One raw
write can therefore be at most 8,323,072 bytes (127 × 64 KiB) when nothing else
is queued. Queued and unacknowledged bytes count against the limit.

`session.write()` is synchronous and returns `void`. If the output is temporarily
full, it throws `OutputPressureError` with code `"OUTPUT_PRESSURE"`, and the call
accepts no bytes. A single write larger than the Session limit throws `RangeError`
before string encoding. SSH does not queue, drop, or replay rejected writes.
Writes after the session closes do nothing. `deny(reason)` always closes the
session and throws `DenyError`, even when it cannot write the reason. The write
error goes to `onError`. Denial never creates a renderer.

Before the renderer attaches, `cols` and `rows` track the requested PTY size.
After it attaches, Core combines window changes while output is pending. The
session dimensions and `onResize` report only the size Core accepts, and the
renderer is already resized.

`end()` and `onClose` mark the logical close, not completed terminal restoration.
Before the renderer attaches, SSH waits for the Session to close before it closes
the channel. After the renderer attaches, SSH waits for `renderer.closed`. Core
allows one second for that output after the close starts. If output fails or the
time expires, the error goes to `onError`, Core cancels pending output without
restoration, and `renderer.closed` rejects. `server.close()` closes every live
session this way. Connection loss and peer shell close cancel the Session at once,
including during middleware. Cleanup does not wait for the ssh2 channel `close`
event, which unread input can delay.

## The three hand-offs

The package gives you a `CliRenderer`. You mount any front-end on
`session.renderer`. Runnable versions of all three are in
[`examples/`](./examples).

### Imperative (`@opentui/core`)

```ts
createServer().serve((session) => {
  const box = new BoxRenderable(session.renderer, { border: true })
  session.renderer.root.add(box)
  // no teardown to wire: the server destroys the renderer when the session closes
})
```

### React (`@opentui/react`)

`createRoot` adopts the existing renderer as it is. See
[`examples/react.tsx`](./examples/react.tsx).

```tsx
import { createRoot } from "@opentui/react"

createServer().serve((session) => {
  const root = createRoot(session.renderer)
  root.render(<App name={session.identity.username} />)
  session.onClose(() => root.unmount()) // your own teardown
})
```

### Solid (`@opentui/solid`)

`render(node, renderer)` checks `instanceof CliRenderer` and **adopts** the
renderer you pass. The app draws onto the SSH channel, not the host terminal.
See [`examples/solid.tsx`](./examples/solid.tsx).

```tsx
import { render } from "@opentui/solid"

createServer().serve(async (session) => {
  // Solid disposes its root when the renderer is destroyed, so there is nothing to wire.
  await render(() => <App name={session.identity.username} />, session.renderer)
})
```

> `@opentui/react` and `@opentui/solid` are **not** runtime dependencies of this
> package. The framework examples use workspace dev dependencies only to show
> the hand-off. Run the Solid example with
> `bun run packages/ssh/examples/solid.tsx`. Its launcher registers the required
> JSX transform before it loads the app.

## Auth & type-flowing identity

`createServer` infers the identity type from your `auth` config.
`session.identity` covers only the methods you enabled, so you can read only the
fields you required. `auth` is optional and defaults to `"open"` (no auth), so
the getting-started snippet works on localhost.

```ts
// publickey-only → fingerprint is guaranteed present
createServer({ auth: { publicKey: "any" } }).serve((s) => s.identity.fingerprint) // ✅ string, no null check

// publickey + password → a union; discriminate on .method
createServer({ auth: { publicKey: "any", password: checkPw } }).serve((s) => {
  if (s.identity.method === "publickey") s.identity.fingerprint // ✅ narrowed
})
```

Supported methods (you can combine them, and the server advertises exactly what
you configure):

| Config                                            | Behavior                                                                                                                           |
| ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `auth: "open"` (or omit)                          | Allow unauthenticated access. This is the default. Listening on a non-loopback host with it logs a warning.                        |
| `publicKey: "any"`                                | Accept **and identify** any key. The server verifies the signature (proof of possession), then sets `identity.fingerprint`.        |
| `publicKey: { allow: (ctx) => boolean }`          | Your own allow/deny over `{ username, fingerprint, publicKey }`. It runs after the signature verifies.                             |
| `publicKey: { authorizedKeys: path \| string[] }` | Allowlist from plain public-key lines. Blank lines and `#` comments are allowed. A line with OpenSSH options throws `ConfigError`. |
| `publicKey: { authorizedKeys, allow }`            | Both. Admit the key if it is on the allowlist **or** `allow` returns true.                                                         |
| `password: (ctx) => boolean`                      | Password check over `{ username, password }`.                                                                                      |
| `keyboardInteractive: (ctx) => boolean`           | Prompt/response flow.                                                                                                              |

> `@opentui/ssh` verifies public-key signatures itself, because `ssh2` does not.
> So `publicKey: "any"` proves possession of the private key instead of trusting
> a claimed key.

For a restricted public-key server, see [`examples/authorized-keys.ts`](./examples/authorized-keys.ts).

## Middleware (`.use()`)

`.use()` wraps your handler with cross-cutting concerns: gating, enrichment, and
logging. Each middleware is one layer of an onion: `(session, next) => Handoff`.
Call `next()` to continue, or `next({ ... })` to add typed fields to
`session.context`. **Return that hand-off.** If you forget, it is a compile error.
To gate, call `session.deny(reason)`. It throws to unwind the chain, so you do
not return it.

1. **Registration order is execution order.** The _first_-registered middleware
   is the _outermost_ link.
2. **`await next()` resolves when the session ends.** The handler is the innermost
   link. The server wraps it so that it does not resolve until the session closes.
   So the `finally` block in `try { return await next() } finally { ... }` runs
   as teardown.
3. **Contributions are inferred.** `next({ tier: "free" })` widens
   `session.context` by `{ tier: string }` with no generic to declare. Each `.use`
   adds to the type, so a later link reads earlier links' typed fields and the
   handler reads all of them.

```ts
import { createServer, type Middleware } from "@opentui/ssh"

// SETUP/TEARDOWN — author a reusable middleware by typing it as `Middleware`. Before
// next() is setup; the finally (after next() resolves when the session closes) is teardown.
const logging: Middleware = async (session, next) => {
  const start = Date.now()
  try {
    return await next() // resolves when the session closes
  } finally {
    console.log(`${session.identity.username} stayed ${Date.now() - start}ms`)
  }
}

createServer({ auth: "open" })
  .use(logging)
  // GATE — deny() throws to bounce; otherwise continue with next().
  .use((s, next) => {
    if (s.identity.username === "banned") s.deny("no entry")
    return next()
  })
  // ENRICH — next({...}) contributes typed context the handler reads.
  .use((_s, next) => next({ tier: "free" as const }))
  .serve((s) => {
    s.context.tier // "free" — typed, no cast
  })
```

Those three patterns (**setup/teardown**, **gate**, and **enrich**) cover almost
every case. Inline arrows need no annotation, because their `identity` and
`context` types come from the builder. To name and reuse one, type it as
`Middleware`, or as `MiddlewareFunction` when it must read upstream context.

A gating middleware's `deny()` runs before the handler, so the handler never runs
**and the server never creates the renderer**. If the server can write the reason,
it appears on the main screen, and the alternate screen is never entered. If the
write fails, `deny()` still closes the session and throws `DenyError`.
Middleware sees a `MiddlewareSession` without the renderer. Only the handler's
`Session` has it. See [`examples/middleware.ts`](./examples/middleware.ts).

### Built-in: `logging`

`@opentui/ssh` includes one ready-made middleware. `logging()` is a
setup/teardown link. It emits a `connect` event on entry and a `disconnect` event
(with duration) on teardown. It **only observes**. It never reports errors, so
`onError` stays the single error sink. A throwing handler is logged as a normal
disconnect _and_ still goes to `onError`.

```ts
import { createServer, logging } from "@opentui/ssh"

createServer({ auth: { publicKey: "any" } })
  .use(logging()) // one line per event to console.log…
  .use(logging({ log: (e) => metrics.record(e) })) // …or a structured sink
  .serve((s) => mountApp(s.renderer))
```

The `log` sink receives a `LogEvent` (`type`, `identity`, `remoteAddress`, `term`,
`cols`/`rows`, and `durationMs` on disconnect). Omit it for a one-line default.

## Host key

```ts
hostKey: {
  path: "./host_key"
} // load if present; else generate ed25519 & persist (0600)
hostKey: {
  pem: "..."
} // provide PEM directly
// omit entirely → ephemeral key, regenerated each start (fine for dev)
```

The first run with a `path` generates and saves an ed25519 key. `listen()` prints
its fingerprint so clients can verify it. When you pass several PEMs, `listen()`
returns and prints every fingerprint in the same order.

## Lifecycle, errors & shutdown

By default, the server allows one shell session per SSH connection and 100
across the server. It rejects excess shell requests without closing the SSH
connection or reporting an error. Adjust both positive-integer limits when an
application needs more concurrency:

```ts
const server = createServer({
  limits: {
    session: {
      perConnection: 2,
      global: 200,
    },
  },
}).serve(handler)
```

A shell counts from the time the server accepts it, before middleware runs. It
keeps its slot until the server closes its SSH channel. These limits bound
application renderers. They do not replace authentication, network access
controls, connection-rate limiting, or process resource limits.

There is no lifecycle event bus and no pluggable logger. The work splits by
**verb**, with no overlap:

- **react** to a session → middleware + `session.onClose` (deny, enrich, tear down)
- **observe** the connection lifecycle → the `logging` middleware (connect/disconnect/duration)
- **report** an error → `onError`, the single error sink

```ts
let live = 0 // server-wide aggregate is just a counter

const server = createServer({
  auth: "open",
  idleTimeout: "10m", // reap a session after no client input ("30s", "500ms", or ms)
  maxTimeout: "1h", // absolute session lifetime, regardless of activity
  startupBanner: true, // set false to silence listen()'s summary
  onError: (err) => console.error(err), // the one error sink; this is the default
}).serve((session) => {
  live++
  session.onClose(() => {
    live-- // your own per-session bookkeeping (the renderer is torn down for you)
  })
})
```

- **The handler and `session.onClose`** are the per-session lifecycle. Set up on
  entry, and tear down when the session closes. Put anything reusable or
  cross-cutting in a middleware.
- **`onError(err)`** is the runtime error sink. Contained application and
  transport errors go here: a throwing handler or middleware, a throwing
  `onResize` or `onClose`, a throwing auth predicate, Session output failures,
  and connection-level and server-level `ssh2` errors. It defaults to
  `console.error`. It _reports_ errors. To react to a session, use middleware or
  `onClose`. To observe the lifecycle, use the `logging` middleware. A bind
  failure rejects `listen()` instead of going here. The logger ignores its own
  sink failures, so logging cannot affect a session.
- **`idleTimeout`** closes a session after that long with **no client input**.
  Any client input restarts the timer. Only the idle session closes. Other sessions
  and the listener continue. Durations must be between `1ms` and `24h`.
- **`maxTimeout`** closes a session after that absolute lifetime, even when the
  client keeps sending input. Durations must be between `1ms` and `24h`.
- **`listen()`** binds, prints the startup banner (URL, host-key fingerprints,
  auth methods, allowlist fingerprints) to stdout unless `startupBanner: false`,
  and returns `{ host, port, fingerprints }`.
- **`close()`** rejects new shell requests and closes every live session, which
  destroys its renderer. Then it ends every client connection and closes the
  listener.

```
@opentui/ssh  ▸  ssh://localhost:2222
host key      SHA256:nThbg6kX…0bGQ  (ssh-ed25519, generated ./host_key)
auth          publickey, password
authorized    2 keys  ·  SHA256:abc… SHA256:def…
```

## License

MIT

---

### Overview (`packages/core/src/testing/README.md`)

# Testing Utilities

Test utilities for opentui terminal UI testing.

## Test Renderer

```ts
import { createTestRenderer } from "@opentui/core/testing"

const { renderer, mockInput, mockMouse, renderOnce, captureCharFrame, resize } = await createTestRenderer({
  width: 80,
  height: 24,
})

// Render once and capture output
await renderOnce()
const output = captureCharFrame()

// Resize terminal
resize(100, 30)
```

## Terminal Capabilities

```ts
import { createTerminalCapabilities, setRendererCapabilities } from "@opentui/core/testing"

const capabilities = createTerminalCapabilities({ kitty_keyboard: true })
setRendererCapabilities(renderer, { notifications: true })
```

## Mock Keyboard Input

```ts
import { createMockKeys, KeyCodes } from "@opentui/core/testing"

const mockInput = createMockKeys(renderer)

// Type text
mockInput.typeText("hello world")
await mockInput.typeText("hello", 10) // 10ms delay between keys

// Press single keys
mockInput.pressKey("a")
mockInput.pressKey(KeyCodes.ENTER)

// Press keys with modifiers
mockInput.pressKey("a", { ctrl: true })
mockInput.pressKey("f", { meta: true })
mockInput.pressKey("z", { ctrl: true, shift: true })
mockInput.pressKey(KeyCodes.ARROW_LEFT, { meta: true })

// Press multiple keys
mockInput.pressKeys(["h", "e", "l", "l", "o"])
await mockInput.pressKeys(["a", "b"], 10) // with delay

// Convenience methods
mockInput.pressEnter()
mockInput.pressEnter({ meta: true })
mockInput.pressEscape()
mockInput.pressTab()
mockInput.pressBackspace()
mockInput.pressArrow("up" | "down" | "left" | "right")
mockInput.pressArrow("left", { meta: true })
mockInput.pressCtrlC()
mockInput.pasteBracketedText("paste content")
```

### KeyCodes

Special keycodes available: `RETURN`, `LINEFEED`, `TAB`, `BACKSPACE`, `DELETE`, `HOME`, `END`, `ESCAPE`, `ARROW_UP`, `ARROW_DOWN`, `ARROW_LEFT`, `ARROW_RIGHT`, `F1`-`F12`

### Modifiers

All `pressKey()`, `pressEnter()`, `pressEscape()`, `pressTab()`, `pressBackspace()`, and `pressArrow()` methods support an optional modifiers object:

```ts
{ ctrl?: boolean; shift?: boolean; meta?: boolean }
```

## Mock Mouse Input

```ts
import { createMockMouse, MouseButtons } from "@opentui/core/testing"

const mockMouse = createMockMouse(renderer)

// Click
await mockMouse.click(x, y)
await mockMouse.click(x, y, MouseButtons.RIGHT)
await mockMouse.click(x, y, MouseButtons.LEFT, {
  modifiers: { ctrl: true, shift: true, alt: true },
  delayMs: 10,
})

// Double click
await mockMouse.doubleClick(x, y)

// Press and release
await mockMouse.pressDown(x, y, MouseButtons.MIDDLE)
await mockMouse.release(x, y, MouseButtons.MIDDLE)

// Move
await mockMouse.moveTo(x, y)
await mockMouse.moveTo(x, y, { modifiers: { shift: true } })

// Drag
await mockMouse.drag(startX, startY, endX, endY)
await mockMouse.drag(startX, startY, endX, endY, MouseButtons.RIGHT, {
  modifiers: { alt: true },
})

// Scroll
await mockMouse.scroll(x, y, "up" | "down" | "left" | "right")
await mockMouse.scroll(x, y, "up", { modifiers: { shift: true } })

// State
const pos = mockMouse.getCurrentPosition() // { x, y }
const buttons = mockMouse.getPressedButtons() // MouseButton[]
```

### MouseButtons

`LEFT` (0), `MIDDLE` (1), `RIGHT` (2)

`WHEEL_UP` (64), `WHEEL_DOWN` (65), `WHEEL_LEFT` (66), `WHEEL_RIGHT` (67)

## Spy

Simple function spy for testing callbacks.

```ts
import { createSpy } from "@opentui/core/testing"

const spy = createSpy()

// Use as callback
someFunction(spy)

// Assertions
spy.callCount() // number
spy.calledWith(arg1, arg2) // boolean
spy.calls // any[][]
spy.reset()
```

## Test Recorder

Record frames during rendering for testing or analysis.

```ts
import { TestRecorder } from "@opentui/core/testing"

const { renderer, renderOnce } = await createTestRenderer({ width: 80, height: 24 })
const recorder = new TestRecorder(renderer)

// Start recording
recorder.rec()

// Add content and trigger renders
const text = new TextRenderable(renderer, { content: "Hello" })
renderer.root.add(text)
await Bun.sleep(1) // Wait for automatic render from add()

text.content = "World"
await Bun.sleep(1) // Wait for automatic render from content change

// Stop recording
recorder.stop()

// Access recorded frames
const frames = recorder.recordedFrames
console.log(`Recorded ${frames.length} frames`)

frames.forEach((frame) => {
  console.log(`Frame ${frame.frameNumber} at ${frame.timestamp}ms:`)
  console.log(frame.frame)
})

// Clear and start new recording
recorder.clear()
recorder.rec()
```

### TestRecorder API

- `rec()` - Start recording frames
- `stop()` - Stop recording frames
- `clear()` - Clear all recorded frames
- `isRecording` - Check if currently recording
- `recordedFrames` - Get array of recorded frames (returns a copy)

### RecordedFrame

Each frame contains:

- `frame: string` - The captured frame content
- `timestamp: number` - Time in milliseconds since recording started
- `frameNumber: number` - Sequential frame number (0-indexed)

## Example

```ts
import { test, expect } from "bun:test"
import { createTestRenderer } from "@opentui/core/testing"

test("button click", async () => {
  const { renderer, mockMouse, renderOnce, captureCharFrame } = await createTestRenderer({ width: 80, height: 24 })

  const clicked = createSpy()
  const button = new Button("btn", { text: "Click me", onClick: clicked })

  renderer.add(button)
  await renderOnce()

  await mockMouse.click(10, 5)
  expect(clicked.callCount()).toBe(1)
})
```

---

### Overview (`packages/examples/src/magick/README.md`)

# Magick Arena

Magick Arena renders a Three.js scene through a two-slot `NativeImagePool` and `ImageRenderable`.
The demo requires Bun and WebGPU.

## Controls

| Key                         | Action                                              |
| --------------------------- | --------------------------------------------------- |
| WASD                        | Move the red wizard                                 |
| Space                       | Pause or resume the scene                           |
| R                           | Reset time and player position                      |
| T                           | Cycle Kitty transport: raw, zlib, file              |
| C                           | Show GPU, image transport, and renderer diagnostics |
| Backtick or `"`             | Toggle the captured console                         |
| `.`                         | Toggle renderer statistics                          |
| Ctrl+G                      | Dump the hit grid                                   |
| Shift+L / Shift+S / Shift+A | Start / stop / use automatic rendering              |
| Escape                      | Return to the selector, or quit in standalone mode  |
| Ctrl+C                      | Quit                                                |
| Q                           | Quit in standalone mode only                        |

Pause stops continuous GPU readbacks, but pending or reset frames can still render.
Diagnostics remain available. Movement stops while the console is open or the terminal
loses focus. Legacy keyboard input uses approximate movement pulses instead of key-release events.

## Rendering

The framebuffer defaults to 640x360 and stays fixed when the terminal resizes. Press `T`
to cycle transport without restarting the scene, including while paused. The status line
shows the requested and effective Kitty transport, or marks it inactive for other image
protocols. File and zlib modes can fall back to raw output. `C` shows the full transport
status. Returning to the selector restores its previous transport setting.

The demo reads GPU pixels into CPU memory. File transport sends a temporary file path,
not a shared GPU image.

The private GPU adapter is experimental and uses pinned bun-webgpu internals. It releases
per-frame GPU objects. Repeated GPU renderer creation still has a separate provider issue,
so the selector reuses one renderer across visits. It releases that renderer when the
terminal renderer closes. Each return to the selector releases scene resources, images,
and input listeners.

---

### Overview (`packages/native/src/bench/README.md`)

# OpenTUI Benchmarks

This directory contains benchmarks for the OpenTUI core library.

## Running Benchmarks

From the `packages/core` directory:

```bash
bun run bench:native

# Include memory statistics
bun run bench:native --mem

# Or from packages/native:
bun run bench
bun run bench --mem
```

## Adding New Benchmarks

To add a new benchmark:

1. Create a new `*_bench.zig` file in the `bench/` directory
2. Import shared types from `bench-utils.zig`:
   ```zig
   const bench_utils = @import("../bench-utils.zig");
   const BenchResult = bench_utils.BenchResult;
   const MemStats = bench_utils.MemStats;
   ```
3. Implement a `pub fn run(allocator: std.mem.Allocator, show_mem: bool) ![]BenchResult` function:
   - Set up any benchmark-specific dependencies (grapheme pool, Unicode data, etc.)
   - Run your benchmarks and collect results
   - Return a slice of `BenchResult` (caller will free it)
   - The `show_mem` flag indicates whether to include memory statistics
4. Import it in `bench.zig`:
   ```zig
   const my_new_bench = @import("bench/my_new_bench.zig");
   ```
5. Call it and print results in `main()`:
   ```zig
   const my_results = try my_new_bench.run(allocator, show_mem);
   defer allocator.free(my_results);
   try bench_utils.printResults(stdout, my_results);
   ```

Each benchmark manages its own dependencies, so you only set up what you need.

See `bench/text-buffer-view_bench.zig` for a complete example.

---

### Overview (`packages/native/src/tests/README.md`)

# Test Suite

This directory contains the test suite for the OpenTUI Zig components.

### Run All Tests

Run this command from `packages/core`:

```bash
bun run test:native
```

It checks the vendored Zig dependency checksums, runs `zig build test`, then
tests and runs `examples/hello`.

### Measure-target lifetime

The default suite already checks that destroying a view or buffer clears
matching Yoga measure targets, then layouts again. For allocator accounting on
that same C export path, run:

```bash
bun run test:native:lifetime
```

That Debug probe turns on GPA leak checks and fails if requested bytes grow
across teardown cycles. Use it after Yoga ownership or measure-target lifetime
changes.

## Adding New Test Files

1. Create a new `*_test.zig` file in this directory
2. Import it in `../test.zig`:
   ```zig
   const new_tests = @import("tests/new_test.zig");
   ```
3. Reference it in the `test` block at the end of `../test.zig`, or Zig skips its tests:
   ```zig
   _ = new_tests;
   ```
4. Update the build system if needed to include any new dependencies

---

### Overview (`packages/web/public/recordings/README.md`)

# Terminal Illustration Recordings

The files in this directory contain terminal recordings for `<TerminalIllustration>`.

The recording tools are in `scripts/recordings/`. They convert an asciinema `.cast` file to a story `.json` file.

Install `asciinema` on your `PATH` before you use the tools. The package supplies `@xterm/headless` as a development dependency.

## Player Options

Each story can set these options:

- `tone="inherit"` recolors cells from their luminance. It uses the component color or the `ink` overrides.
- `tone="recorded"` uses the recorded foreground colors.
- `background="transparent"` does not paint cell backgrounds.
- `background="recorded"` paints recorded backgrounds and restores the recorded default foreground.
- `padding={false}` removes the terminal-colored margin added around a recorded background.
- `fadeTop`, `fadeRight`, `fadeBottom`, and `fadeLeft` set an edge fade from `0` to `1`.
- `title` and `caption` supply text for the controls.

The default values are `tone="inherit"` and `background="transparent"`.

Use `tone="recorded"` for content in which color conveys information. Use a recorded background when foreground contrast depends on it.

The player calculates fades from cell opacity to keep glyphs sharp. Horizontal fades use the midpoint of each styled run.

Do not use a horizontal fade for prose with long runs. Set `fadeLeft` and `fadeRight` to `0`.

Multiple stories and chapters play in order and repeat. The `controls` option accepts these values:

- `hidden`: Do not show controls. This is the default.
- `hover`: Show overlay controls on hover or focus.
- `visible`: Always show overlay controls.
- `below`: Show controls and captions below the recording.

The player loads a recording when it is within `800px` of the viewport.

See `StoryConfig` and `Props` in `src/components/TerminalIllustration.astro` for all options and defaults.

## Use In Scrollback

Use the Scrollback wrapper in an `.mdx` post:

```mdx
import ScrollbackRecording from "../../components/ScrollbackRecording.astro"

<ScrollbackRecording
  label="OpenTUI terminal recording"
  stories={[
    {
      src: "/recordings/anomaly.json",
      title: "OpenTUI",
      tone: "inherit",
    },
  ]}
/>
```

Use `controls="below"` when chapter captions are part of the article.

## Record A Cast

Run the record tool from `packages/web`:

```bash
scripts/recordings/bin/record out.cast
scripts/recordings/bin/record out.cast -- node demo.mjs
scripts/recordings/bin/record --headless out.cast -- node demo.mjs
scripts/recordings/bin/record --window-size 100x56 out.cast -- bun src/app.ts
```

The tool is also available as `bun run recordings:record`.

If the application uses another working directory, store the absolute tool path first:

```bash
record="$PWD/scripts/recordings/bin/record"
```

### Automatic Applications

Use `--headless` for an application that needs no input. Use `timeout` to set the duration.

```bash
cd ~/src/tuiexperiments/some-headless-app
"$record" --headless --window-size 210x42 /tmp/my-recording.cast \
  -- timeout 16 bun src/app.ts
```

Use a wide, short window for a recording that fills a wide media area.

### Applications With Setup Input

Use `tmux` when an application needs a small amount of setup input:

```bash
tmux new-session -d -s rec -x 200 -y 46
tmux send-keys -t rec -l "cd ~/src/tuiexperiments/some-app && $record /tmp/my-recording.cast -- bun start"
tmux send-keys -t rec Enter
sleep 3
tmux send-keys -t rec -l 's'
sleep 30
tmux send-keys -t rec -l 'q'
tmux kill-session -t rec
```

Make sure that setup keys changed the application before you keep a long recording.

Use `tmux capture-pane -e -p` to examine colors and escape sequences. Plain-text output cannot confirm a color change.

### Interactive Applications

Record continuous interaction directly:

```bash
cd ~/src/tuiexperiments/some-app
"$record" /tmp/my-recording.cast -- bun src/app.ts
```

Use `ctrl+x` to add chapter markers. Use many small inputs when motion must appear continuous.

The build tool keeps only changed frames. A few large inputs produce only a few frames.

## Build A Story

Copy the cast to the tool directory and build it:

```bash
cp /tmp/my-recording.cast scripts/recordings/my-recording.cast
bun run recordings:build scripts/recordings/my-recording.cast
```

The first build creates `scripts/recordings/my-recording.story.config.json`. It also writes `public/recordings/my-recording.json`.

Edit the generated config to set chapter boundaries, captions, and durations. Then build from the config:

```bash
bun run recordings:build scripts/recordings/my-recording.story.config.json
```

Commit the config and the generated story. The repository ignores the cast.

## Check The Story

Check these items before you ship the story:

- Set `atMs` after transient shell output and before the first useful frame.
- Remove persistent title bars, borders, labels, and instructions that do not belong in the composition.
- Set `durationMs` to one cycle for a deterministic animation. The player repeats the story.
- Make sure that setup keys changed the recorded mode. Examine `rows` when you must confirm cell colors.
- Use a recorded tone for prose, tables, and other content that must remain legible.
- Check the final aspect ratio after each crop.

The `fit="cover"` mode sets the font size from the available height. A narrow recording can leave horizontal empty space.

Use this minimum ratio for a full-width composition:

```text
cols / rows >= (boxWidth / boxHeight) * (lineToEm / chToEm)
```

For OpenTUI Mono, `chToEm` is approximately `0.6` and `lineToEm` is approximately `1.1`.

A media-area ratio from `1.2` to `1.3` usually needs a recording ratio from approximately `2.2` to `2.4`.

Record a wider grid or crop rows if the recording does not meet the minimum ratio.

## Ship The Story

Reference the generated file from a page or a Scrollback post:

```astro
<TerminalIllustration
  label="OpenTUI terminal recording"
  stories={[{ src: "/recordings/my-recording.json", tone: "inherit" }]}
  controls="visible"
/>
```

The build writes the file to `public/recordings/`. Do not copy it after the build.

---

### Overview (`packages/web/src/font/README.md`)

# OpenTUI Mono

OpenTUI Mono is the typeface for the site. Iosevka compiles
`private-build-plans.toml` into static WOFF2 files. Iosevka does not write
an OpenType variable font.

The live files are Regular, Bold, Italic, and Bold Italic in
`public/fonts/`. `/lab/font` compares those files with the next compile in
`public/fonts/candidate/`.

## Plan

The plan sets glyphs, metrics, weights (100 to 900), and widths (Normal +
SemiCondensed). Ligatures are off. The plan makes one web-subset family.
Change keys in the plan. Do not start from a blank UFO or FontForge file.

Identity glyphs: `a g l I 1 0 8 6 9 @ { } ( ) _ *`.

These keys change the look first: `sb`, width `shape`, `xHeight`,
`archDepth`, and the identity keys in `variants.design`.

## Compile

You need Node 18+ and a clone of [Iosevka](https://github.com/be5invis/Iosevka)
at `v34.4.0`. You do not need `ttfautohint` for `webfont-unhinted`.

```
git clone --depth 1 --branch v34.4.0 https://github.com/be5invis/Iosevka.git
cp packages/web/src/font/private-build-plans.toml Iosevka/
cd Iosevka && npm install
npm run build -- --jCmd=4 webfont-unhinted::OpenTUIMono
```

Copy the next compile into `packages/web/public/fonts/candidate/` as
`OpenTUIMono-{Regular,Bold,Italic,BoldItalic}.woff2`. Compare it with
the live files at `/lab/font`. When you accept the next compile, copy those
four files to `packages/web/public/fonts/`.

---

### Overview (`packages/core/src/renderables/composition/README.md`)

# Composition Exploration

This is a simple exploration of how to compose renderables into a tree.

It's not react and not reactive, it's just a way to compose renderables
and mount them into a parent container.

It's a work in progress and not in any way meant to be a replacement for react.

---

### Overview (`packages/core/src/lib/tree-sitter/assets/README.md`)

# Tree-sitter Assets

This directory contains downloaded tree-sitter language parsers and highlight queries for the default parsers used by OpenTUI.
They are included in the repository to avoid downloading them every time the project is built or tests are run.

## Asset Management

Parser definitions are configured in `../parsers-config.ts` for OpenTUI's built-in parser set.

### Update Script

The `update.ts` script downloads parsers and queries from the configured URLs and generates the `../default-parsers.ts` file.

#### Usage

**For OpenTUI Core Development (using default paths):**

```bash
# Run from this directory
bun update.ts

# Or from the project root
bun packages/core/src/lib/tree-sitter/assets/update.ts
```

**For Application Developers (using custom paths):**

```bash
# CLI usage with custom paths
bun update.ts \
  --config ./my-parsers-config.json \
  --assets ./src/tree-sitter/assets \
  --output ./src/tree-sitter/parsers.ts

# Show help
bun update.ts --help
```

**Programmatic Usage:**

```typescript
import { updateAssets } from "@opentui/core/tree-sitter/update-assets"

await updateAssets({
  configPath: "./my-parsers-config.json",
  assetsDir: "./src/tree-sitter/assets",
  outputPath: "./src/tree-sitter/parsers.ts",
})
```

#### What it does

1. **Downloads Language Parsers**: Downloads `.wasm` files from GitHub releases
2. **Downloads Query Files**: Downloads `.scm` highlight query files from repositories
3. **Combines Queries**: For languages like TypeScript, combines multiple query files (JS base + TS extensions)
4. **Generates Imports**: Creates a TypeScript file with proper file imports using Bun's `with { type: "file" }` syntax

## Adding New Parsers

### For Application Developers

If you're using OpenTUI in your application and want to add support for additional languages:

#### 1. Create a parsers configuration file

Create a `parsers-config.json` file in your project with the structure:

```json
{
  "parsers": [
    {
      "filetype": "python",
      "wasm": "https://github.com/tree-sitter/tree-sitter-python/releases/download/v0.20.4/tree-sitter-python.wasm",
      "queries": {
        "highlights": [
          "https://raw.githubusercontent.com/tree-sitter/tree-sitter-python/refs/heads/master/queries/highlights.scm"
        ]
      }
    }
  ]
}
```

#### 2. Add to your build pipeline

Add the update script to your `package.json`:

```json
{
  "scripts": {
    "prebuild": "bun node_modules/@opentui/core/lib/tree-sitter/update-assets.js --config ./parsers-config.json --assets ./src/parsers --output ./src/parsers.ts",
    "build": "bun build ./src/index.ts"
  }
}
```

#### 3. Use the generated parsers

```typescript
import { getTreeSitterClient } from "@opentui/core"
import { getParsers } from "./parsers"

const client = getTreeSitterClient()

// Register your custom parsers
for (const parser of getParsers()) {
  client.addFiletypeParser(parser)
}
```

For more information about using Tree-Sitter in your application, see the [Tree-Sitter guide](https://opentui.com/docs/reference/tree-sitter).

### For OpenTUI Core Developers

To add a new default parser to OpenTUI Core:

1. **Update Configuration**: Add the new parser to `../parsers-config.ts`
2. **Run Update Script**: Execute `bun update.ts` to download assets and regenerate imports
3. **Commit Generated Assets**: Include the downloaded parser/query files and regenerated `../default-parsers.ts`

---

### Packages Web Src Content Docs Getting Started (`packages/web/src/content/docs/getting-started.mdx`)

---
title: Getting started
description: Start with one OpenTUI quickstart, then choose a framework
skill:
  entry: true
  intents: [getting-started, intro, examples, agent-skill]
---

# Getting started

OpenTUI is a native terminal UI core written in Zig with TypeScript bindings.

It renders a component tree, lays it out with Flexbox, handles terminal input, and updates the cells that changed.
OpenTUI's TypeScript packages call the Zig library through its native interface. OpenTUI powers OpenCode in production.

## Start with the quickstart

[Build your first app in the quickstart](/docs/getting-started/quickstart). In the quickstart, you create a project
from an empty directory, compose a small interface, handle keyboard input, and run it in your terminal.

The quickstart uses `@opentui/core` directly to teach the renderer, component tree, input, and layout model once. You
do not need to choose a framework before you complete it.

Check [Runtime and platform support](/docs/getting-started/runtime-support) before you use Node.js, cross-compile, or
deploy to another target.

## Choose a framework

- [`@opentui/core`](/docs/core-concepts/renderables) uses imperative renderables and events directly.
- [`@opentui/react`](/docs/bindings/react) uses React components, hooks, and state.
- [`@opentui/solid`](/docs/bindings/solid) uses Solid components, signals, and effects.

If you use Core, read [Renderables](/docs/core-concepts/renderables) after the quickstart.

## Find a topic

- [Components](/docs/components) compares component availability across Core, React, and Solid.
- [Renderer](/docs/core-concepts/renderer) covers terminal setup, the root, scheduling, and central events.
- [Layout](/docs/core-concepts/layout) covers Yoga-based sizing and positioning.
- [Interaction, focus, and selection](/docs/core-concepts/interaction) explains shared pointer and focus behavior.
- [Testing](/docs/core-concepts/testing) renders apps in memory for assertions and snapshots.
- [Package entry points](/docs/reference/package-entrypoints) lists supported import paths.
- [Lifecycle and cleanup](/docs/core-concepts/lifecycle) owns terminal restoration and resource release.
- [Native rendering](/docs/native/overview) explains the scene model and native C and Zig interfaces.

## Explore the examples

Run the OpenTUI example browser without cloning the repository. Release executables support macOS x64 and arm64,
Linux x64, and Windows x64. On those targets, including x64 WSL and Git Bash:

```bash
curl -fsSL https://raw.githubusercontent.com/anomalyco/opentui/main/packages/examples/install.sh | sh
```

On Windows, download the latest release from [GitHub Releases](https://github.com/anomalyco/opentui/releases/latest).

## Give an agent the docs

OpenTUI includes a skill that teaches AI coding assistants its APIs and patterns. It holds these docs and the release
notes. Install the skill of the latest release with [`npx skills`](https://skills.sh):

```bash
npx skills add https://opentui.com --skill opentui
```

Add `-g` to install the skill globally for every project. OpenCode uses the same install command. `npx skills update`
moves the skill to the newest release. For the main branch, install from `https://opentui.com/docs/next` instead.

A skill installed with `npx skills add anomalyco/opentui` follows the main branch. Run the command above to switch it
to releases.

## Versions

These docs describe one release. The site header names it, and `next` beside it opens the docs of the main branch,
which include unreleased changes. [Releases](/docs/releases) lists what changed in each release, and the [API reference](/docs/api)
names the release that added each export. Above its previous and next links, a page lists its changes in recent
releases.

---

### Packages Web Src Content Docs Components Overview (`packages/web/src/content/docs/components/overview.mdx`)

---
title: Components
description: Choose a component and compare Core, React, and Solid availability
skill:
  entry: true
  intents: [components, component, component-support, support-matrix, react-components, solid-components]
---

# Components

The tables compare each component's availability across Core, React, and Solid.

An automatic framework element needs no component-specific registration. Separately registered elements require the package setup shown on their component page. Unavailable means that the shipped API does not expose that surface.

## Display and layout

- [Text](/docs/components/text) displays labels, prose, and styled text. Choose Code for parsed source or Markdown for documents.
- [Box](/docs/components/box) lays out children and can draw a background, border, and title. Choose ScrollBox when the children must scroll.

| Component | Package         | Core renderable  | React                | Solid                | Status   |
| --------- | --------------- | ---------------- | -------------------- | -------------------- | -------- |
| Text      | `@opentui/core` | `TextRenderable` | `<text>` (automatic) | `<text>` (automatic) | Built in |
| Box       | `@opentui/core` | `BoxRenderable`  | `<box>` (automatic)  | `<box>` (automatic)  | Built in |

### Inline text elements

React and Solid also register `<span>`, `<b>`, `<strong>`, `<i>`, `<em>`, `<u>`, `<br>`, and `<a>`. These elements are
built-in text children, not standalone layout components. Use them only inside `<text>`. The `<a>` element accepts
`href` and creates terminal hyperlink metadata.

| Surface        | Package                            | Core renderable      | React                                             | Solid                                             | Status                        |
| -------------- | ---------------------------------- | -------------------- | ------------------------------------------------- | ------------------------------------------------- | ----------------------------- |
| Inline styling | `@opentui/react`, `@opentui/solid` | `TextNodeRenderable` | `<span>`, `<b>`, `<strong>`, `<i>`, `<em>`, `<u>` | `<span>`, `<b>`, `<strong>`, `<i>`, `<em>`, `<u>` | Built-in framework text nodes |
| Line break     | `@opentui/react`, `@opentui/solid` | `TextNodeRenderable` | `<br>`                                            | `<br>`                                            | Built-in framework text node  |
| Hyperlink      | `@opentui/react`, `@opentui/solid` | `TextNodeRenderable` | `<a href>`                                        | `<a href>`                                        | Built-in framework text node  |

## Input and selection

- [Input](/docs/components/input) edits one line of text. Choose Textarea for multi-line editing.
- [Textarea](/docs/components/textarea) edits multiple lines with cursor and selection support. Choose Input for one line.
- [Select](/docs/components/select) shows discrete choices in a vertical list. Choose TabSelect for a horizontal strip or Slider for a continuous value.
- [TabSelect](/docs/components/tab-select) shows peer choices in a horizontal tab strip. Choose Select for a vertical list.
- [Slider](/docs/components/slider) selects a continuous value with a draggable thumb. Choose Select for discrete choices.

| Component | Package         | Core renderable       | React                      | Solid                      | Status                   |
| --------- | --------------- | --------------------- | -------------------------- | -------------------------- | ------------------------ |
| Input     | `@opentui/core` | `InputRenderable`     | `<input>` (automatic)      | `<input>` (automatic)      | Built in                 |
| Textarea  | `@opentui/core` | `TextareaRenderable`  | `<textarea>` (automatic)   | `<textarea>` (automatic)   | Built in                 |
| Select    | `@opentui/core` | `SelectRenderable`    | `<select>` (automatic)     | `<select>` (automatic)     | Built in                 |
| TabSelect | `@opentui/core` | `TabSelectRenderable` | `<tab-select>` (automatic) | `<tab_select>` (automatic) | Built in                 |
| Slider    | `@opentui/core` | `SliderRenderable`    | Unavailable                | Unavailable                | Built-in Core renderable |

## Scrolling

- [ScrollBox](/docs/components/scrollbox) owns scrollable children, clipping, and scrollbars. Use it for normal scrolling containers.
- [ScrollBar](/docs/components/scrollbar) controls an external scroll position. Use it when another model owns the scroll state.

| Component | Package         | Core renderable       | React                     | Solid                     | Status                   |
| --------- | --------------- | --------------------- | ------------------------- | ------------------------- | ------------------------ |
| ScrollBox | `@opentui/core` | `ScrollBoxRenderable` | `<scrollbox>` (automatic) | `<scrollbox>` (automatic) | Built in                 |
| ScrollBar | `@opentui/core` | `ScrollBarRenderable` | Unavailable               | Unavailable               | Built-in Core renderable |

## Rich content

- [Code](/docs/components/code) displays syntax-highlighted source. Choose Markdown for documents or Diff for patches.
- [Markdown](/docs/components/markdown) renders document structure and fenced code. Choose Code when all content is source code.
- [Line number gutter](/docs/components/line-number) adds numbers and signs to line-aware content. Diff manages its own gutters.
- [Diff](/docs/components/diff) displays the first file patch in unified or split form. Create one instance for each file patch.
- [TextTable](/docs/components/text-table) lays out styled cell data. Choose Markdown when the table starts as Markdown source.

| Component          | Package         | Core renderable        | React                       | Solid                                                                                    | Status                                  |
| ------------------ | --------------- | ---------------------- | --------------------------- | ---------------------------------------------------------------------------------------- | --------------------------------------- |
| Code               | `@opentui/core` | `CodeRenderable`       | `<code>` (automatic)        | `<code>` (automatic)                                                                     | Built in                                |
| Markdown           | `@opentui/core` | `MarkdownRenderable`   | `<markdown>` (automatic)    | `<markdown>` (automatic)                                                                 | Built in                                |
| Line number gutter | `@opentui/core` | `LineNumberRenderable` | `<line-number>` (automatic) | `<line_number>` is runtime built in. The Solid binding lacks an exact props declaration. | Built in with a Solid typing limitation |
| Diff               | `@opentui/core` | `DiffRenderable`       | `<diff>` (automatic)        | `<diff>` is runtime built in. The Solid binding lacks an exact props declaration.        | Built in with a Solid typing limitation |
| TextTable          | `@opentui/core` | `TextTableRenderable`  | Unavailable                 | Unavailable                                                                              | Built-in Core renderable                |

OpenTUI bundles parsers for JavaScript and JSX, TypeScript and TSX, Markdown and Markdown inline, and Zig. Other grammars require [Tree-sitter configuration](/docs/reference/tree-sitter).

## Graphics and media

- [ASCIIFont](/docs/components/ascii-font) draws short text with bundled ASCII art fonts. Choose Text for labels and prose.
- [FrameBuffer](/docs/components/frame-buffer) exposes direct cell drawing. Choose Image when you only need to display an image source.
- [Image](/docs/components/image) displays encoded images or a `NativeImage`. Use the [`NativeImage` API](/docs/reference/native-image) to decode or transform pixels.
- [QR code](/docs/components/qr-code) displays text as a scannable symbol. Use the [QR encoder](/docs/reference/qr-encoder) for matrices and serialized output.
- [Embedded terminal](/docs/components/embedded-terminal) parses VT output and draws a child terminal. It does not start a process.

| Component         | Package           | Core renderable              | React                                                  | Solid                                                  | Status                   |
| ----------------- | ----------------- | ---------------------------- | ------------------------------------------------------ | ------------------------------------------------------ | ------------------------ |
| ASCIIFont         | `@opentui/core`   | `ASCIIFontRenderable`        | `<ascii-font>` (automatic)                             | `<ascii_font>` (automatic)                             | Built in                 |
| FrameBuffer       | `@opentui/core`   | `FrameBufferRenderable`      | Unavailable                                            | Unavailable                                            | Advanced                 |
| Image             | `@opentui/core`   | `ImageRenderable`            | `<image>` (automatic)                                  | `<image>` (automatic)                                  | Built in                 |
| QR code           | `@opentui/qrcode` | `QRCodeRenderable`           | Register `@opentui/qrcode/react`, then use `<qr-code>` | Register `@opentui/qrcode/solid`, then use `<qr_code>` | Separately registered    |
| Embedded terminal | `@opentui/core`   | `EmbeddedTerminalRenderable` | Unavailable                                            | Unavailable                                            | Built-in Core renderable |

Use the [Buffer API](/docs/reference/buffer-api) for low-level `OptimizedBuffer` ownership and drawing operations.

## Diagnostics

[`TimeToFirstDraw`](/docs/components/time-to-first-draw) is a diagnostic, not a general display component. See [Rendering diagnostics](/docs/test-and-debug/rendering-diagnostics) for its timestamp behavior.

---

### Packages Web Src Content Docs Getting Started Quickstart (`packages/web/src/content/docs/getting-started/quickstart.mdx`)

---
title: Quickstart
description: Build and run an interactive OpenTUI app with Bun
skill:
  entry: true
  intents: [installation, quickstart]
---

{/* docs-lint-disable mdx-esm-import */}
import ScrollbackRecording from "../../../components/ScrollbackRecording.astro"

# Quickstart

Build a small counter controlled by the arrow keys. The finished app uses layout, text, keyboard input, live updates,
and explicit cleanup.

## Prerequisite

This guide uses [Bun](https://bun.sh) to run TypeScript source. See [Runtime and platform support](/docs/getting-started/runtime-support)
for other runtime and platform requirements.

Create an empty project and add OpenTUI:

```bash
mkdir my-tui && cd my-tui
bun init -y
bun add @opentui/core
```

## Create the app

Create `index.ts`:

```typescript
import { BoxRenderable, TextRenderable, createCliRenderer } from "@opentui/core"

const renderer = await createCliRenderer({
  exitOnCtrlC: true,
  backgroundColor: "#1131E9",
})

let count = 0
const counter = new TextRenderable(renderer, {
  id: "counter",
  content: "Count  0",
  fg: "#FFFFFF",
})

const panel = new BoxRenderable(renderer, {
  width: 42,
  height: 9,
  backgroundColor: "#1131E9",
  alignItems: "center",
  justifyContent: "center",
})
const content = new BoxRenderable(renderer, {
  width: 38,
  height: 7,
  backgroundColor: "#2947F0",
  padding: 1,
  flexDirection: "column",
  gap: 1,
  alignItems: "center",
})

content.add(new TextRenderable(renderer, { content: "Hello, OpenTUI!", fg: "#DCE3FF" }))
content.add(counter)
content.add(new TextRenderable(renderer, { content: "left/right change | q quit", fg: "#AEBBFF" }))
panel.add(content)
renderer.root.add(panel)

renderer.keyInput.on("keypress", (key) => {
  if (key.name === "q") {
    renderer.destroy()
    return
  }

  if (key.name === "left") count--
  else if (key.name === "right") count++
  else return

  counter.content = `Count  ${count}`
})
```

`createCliRenderer()` takes control of the terminal and returns a `CliRenderer`. Its `root` property is the root of the
component tree. `BoxRenderable` and `TextRenderable` are imperative tree nodes. The event handler updates the counter's
`content` property directly.

The renderer emits parsed keyboard events through `keyInput`. The handler changes `counter.content`, which schedules a
new frame.

## Run it

```bash
bun index.ts
```

Press the Left and Right arrow keys. The count updates in place.

The application creates and owns the renderer. It must call `renderer.destroy()` on every shutdown path to release
resources and restore the terminal. This example calls it when you press `q`. `exitOnCtrlC: true` handles the `Ctrl+C`
path.

{/* docs-lint-disable mdx-component-node */}

<ScrollbackRecording
  label="The quickstart app changing a count with arrow keys, then returning to the shell"
  controls="hidden"
  stories={[
    {
      src: "/recordings/quickstart.json",
      tone: "recorded",
      background: "recorded",
      padding: false,
    },
  ]}
/>

## Continue

- Return to the [OpenTUI introduction](/docs) for the documentation map.
- [React bindings](/docs/bindings/react) apply the shared model through React.
- [Solid bindings](/docs/bindings/solid) apply the shared model through Solid.
- [Components](/docs/components) compares component availability across Core, React, and Solid.
- [Renderables](/docs/core-concepts/renderables) covers imperative tree creation and mutation.
- [Lifecycle and cleanup](/docs/core-concepts/lifecycle) covers shutdown paths and resource ownership.

---

### Packages Web Src Content Docs Keymap Overview (`packages/web/src/content/docs/keymap/overview.mdx`)

---
title: Keymap
description: Register, dispatch, and query shared key bindings in an OpenTUI app
skill:
  entry: true
  intents: [keymap, keybindings, shortcuts, commands, leader, ex-commands]
---

# Keymap

`@opentui/keymap` is an independent, host-agnostic package for layered key bindings, commands, and key sequences.

The package lets an app register an action once and resolve it from the current focus. An editor can save from every
view, close only the focused dialog, and show its current shortcuts.

## Install

```bash
bun add @opentui/keymap
```

The engine is JavaScript. The OpenTUI adapter connects it to terminal input from `CliRenderer`.
The HTML adapter connects the same engine to browser keyboard events.

## Create an OpenTUI keymap

Create the renderer first. Then create a default OpenTUI keymap and register an application layer.

```ts
import { createCliRenderer } from "@opentui/core"
import { createDefaultOpenTuiKeymap } from "@opentui/keymap/opentui"

const renderer = await createCliRenderer()
const keymap = createDefaultOpenTuiKeymap(renderer)

keymap.registerLayer({
  commands: [
    {
      name: "file.save",
      run() {
        console.log("save")
      },
    },
    {
      name: "app.quit",
      run() {
        renderer.destroy()
      },
    },
  ],
  bindings: [
    { key: "ctrl+s", cmd: "file.save" },
    { key: "q", cmd: "app.quit" },
  ],
})
```

`createDefaultOpenTuiKeymap()` installs the default parser, event matcher, `enabled` fields, and metadata fields.
Each registration returns a disposer. Destroying the renderer also ends the host lifecycle and releases keymap resources.
See [Lifecycle and cleanup](/docs/core-concepts/lifecycle) for application shutdown.

```text terminal=keymap-active-keys
ctrl+s  file.save
q       app.quit
```

## Register, dispatch, and query

Keymap uses one short model:

| Step     | What happens                                                                                                   | Main APIs                                                          |
| -------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| Register | The app adds commands, key bindings, layers, tokens, patterns, and addons.                                     | `registerLayer()`, `registerToken()`, addon registration functions |
| Dispatch | The host sends an event. The engine applies focus, conditions, layer order, sequences, and command resolution. | Host listeners, `dispatchCommand()`, intercepts                    |
| Query    | The app reads the commands and next keys that are active now.                                                  | `getActiveKeys()`, `getCommands()`, `getPendingSequence()`         |

A layer can be global or attached to a host target. Higher `priority` values run first.
Newer layers run first when priorities match. A focused target controls which local layers are active.

A key sequence stays pending until another key completes it, rejects it, or clears it.
Queries use that pending state, so a shortcut view can show only the next reachable keys.

The [Core keymap API](/docs/keymap/core) defines key syntax, field compilation, runtime data, command queries,
dispatch results, ordering, errors, and sequence limits.

## Choose the input level

Use [direct keyboard events](/docs/core-concepts/keyboard) when one owner needs the original `KeyEvent`.
Examples include a global Ctrl+C handler or terminal-protocol diagnostics.

Use a component's local key binding options for behavior that belongs only to that component.
Examples include cursor movement in an input or selection movement in a list.

Move to Keymap when the same application command has more than one owner or context. Keymap is useful when you need:

- focus-scoped or priority-ordered layers
- user-configurable key bindings
- multi-key sequences or leader keys
- named commands for palettes and help
- one command model across Core, React, and Solid code

Keymap does not replace component text input. The OpenTUI textarea addon can route editing commands through Keymap when
an application needs to manage those key bindings centrally.

## Hosts and framework state

The [Hosts](/docs/keymap/hosts) page defines `KeymapHost` and the built-in OpenTUI and HTML adapters.
Hosts supply focus, hierarchy, key events, metadata, and lifecycle signals.

React and Solid do not create a keymap. Their providers consume a pre-created OpenTUI keymap and expose reactive state:

- [React keymap integration](/docs/keymap/react) supplies React context and hooks.
- [Solid keymap integration](/docs/keymap/solid) supplies Solid context, selectors, and hooks.

## Add features

The bare engine has no fixed key language. Addons install parsers, field compilers, sequence behavior, diagnostics, and
OpenTUI integrations through public registration APIs.

- [Built-in keymap addons](/docs/keymap/addons) lists every shipped universal and OpenTUI addon.
- [Custom keymap addons](/docs/keymap/custom-addons) explains addon callbacks, ordering, cleanup, and tests.

## Next

- [Hosts](/docs/keymap/hosts)
- [Core keymap API](/docs/keymap/core)
- [React keymap integration](/docs/keymap/react)
- [Solid keymap integration](/docs/keymap/solid)
- [Built-in keymap addons](/docs/keymap/addons)
- [Custom keymap addons](/docs/keymap/custom-addons)

---

### Packages Web Src Content Docs Native Overview (`packages/web/src/content/docs/native/overview.mdx`)

---
title: Native rendering
description: Follow a box and a label from a native node tree to terminal cells and output
skill:
  entry: true
  intents: [native, native-rendering, native-scene, native-api]
---

# Native rendering

OpenTUI's native library turns a tree of UI nodes into terminal output.
You can call it from C or Zig without JavaScript.

This walkthrough follows one small interface: a bordered box containing the label `Hello`.
Its drawing area is 12 columns wide and three rows high:

```text frame=hello
┌──────────┐
│Hello     │
└──────────┘
```

Your program describes the box and label. The native library calculates their positions, draws their cells, and produces terminal output.
The program then delivers that output to its destination.

After the first frame, you will change the label to `Ready` and render again without rebuilding the tree.

## Describe the interface as a tree

The box and label need three native _nodes_. Each node stores one part of the interface:

```text
┌─ Scene ─────────────────────────┐
│ root                            │
│ └─ box: single-line border      │
│    └─ label: "Hello"            │
└─────────────────────────────────┘
```

The root is the top of the tree. The box contains the label.
Adding the label to the box establishes that relationship. It does not specify where to draw the first character.

Nodes store layout properties, such as width and padding, and drawing properties, such as background color and borders.
A text node also stores its content. Node creation, property changes, and parent-child relationships are native API operations.

The native library keeps this tree, or _scene_, between frames.
This is the _retained scene_ model: change the nodes you already have, then render them again.
You do not submit a fresh list of drawing commands for each frame.

## Calculate positions and sizes

_Layout_ turns the tree's relationships and size rules into positions and dimensions.
The scene uses Yoga, a flexbox layout engine, with one Yoga node for each scene node.
Dimensions count terminal cells, not pixels.

The root fills the drawing area, and the box stretches across it.
The border takes one cell on each side, leaving ten cells for the label.
That places `Hello` at column 1, row 1, just inside the border.
Coordinates start at zero.

You did not calculate the text position yourself. The border and parent-child relationship determine it.
The same layout rules also handle padding, alignment, flexible sizes, and nested containers.

When you change a layout property, the next layout pass calculates the result.
Layout queries read stored geometry. They do not start a new pass.

## Draw a grid of cells

After layout, the scene _paints_ into an in-memory grid called the framebuffer.
The box draws its border, then the label draws `Hello` inside it.
Each cell stores character information, foreground and background colors, and attributes such as bold or underline.

The `H` in `Hello` occupies one cell. Other text can occupy several cells or combine several code points into one displayed character.
Text drawing APIs handle that conversion. A UTF-8 byte count is not a terminal-cell width.

Painting changes the framebuffer, not the terminal. No terminal connection is necessary at this stage.

## Send the result to an output destination

A terminal accepts bytes, not a node tree or a framebuffer.
The native renderer encodes the cells as text, cursor movement, colors, and other control sequences.
It compares cells with its existing encoding state, so an update need not send the entire screen again.

The bytes wait in an output queue until your program delivers them.
Your program can copy them out and write them itself.
It can also let the native library write them to standard output or pass them to a writer function that your program supplies.
The delivery path is the _transport_. You can write to a file, a local terminal, or a connection.
The diagram shows the copy path:

```text
┌─ Native library ────────────────┐
│ Scene nodes                     │
│   ↓ layout and painting         │
│ Grid of terminal cells          │
│   ↓ terminal encoding           │
│ Queued output bytes             │
└────────────────┬────────────────┘
                 ↓ copy
┌─ Your program ──────────────────┐
│ Write bytes to the destination  │
│ Report completed delivery       │
└─────────────────────────────────┘
```

A slow connection can still be sending a frame after painting finishes.
The program reports delivery as bytes reach the transport. Once delivery covers the frame's output, the native library marks that frame as presented.
This does not prove that a remote terminal visibly displayed it.

## Change the label, not the tree

After the first frame completes, set the label's text to `Ready` and repeat painting, encoding, and delivery:

```text frame=ready
┌──────────┐
│Ready     │
└──────────┘
```

The root, box, and label are the same nodes. Their relationships and border settings did not change.
Only the label's content changed. The retained scene produces the new frame.

Layout, painting, and encoding still have a cost.
Retaining the scene means your program describes changes instead of directing every node's drawing on every frame.

## Build the program around the scene

Your program is the _host_. It owns application state and decides when to render.
The native library does not start an application event loop or a rendering worker.

An interactive host reads input, changes nodes, and requests frames.
The programs in the language guides create this box and label, render `Hello`, then change it to `Ready`.
They write both frames to files, using the same operations as a terminal application.

Continue with [Resources and ownership](/docs/native/resources) for object lifetimes, then [Frames and output](/docs/native/frames) for drawing and delivery.

The C application binary interface (ABI) is experimental. Use the native library, headers, and bindings from the same revision.

Core provides a TypeScript application API over this library through `Renderable` and `CliRenderer`, not the native frame protocol.
[How Core uses native](/docs/native/core) explains that integration separately.

Read [Host I/O and time](/docs/native/host-io-time) before connecting a terminal transport.
Then choose the [C](/docs/native/c) or [Zig](/docs/native/zig) guide to build and run the box-and-label program.

---

### Packages Web Src Content Docs Reference Api Index (`packages/web/src/content/docs/reference/api-index.mdx`)

---
title: API and symbol index
description: Find every published OpenTUI entry point and exported symbol, with its runtime, support class, and canonical documentation
skill:
  entry: true
  intents: [api, symbols, exports, public-api, api-index, lookup]
---

# API and symbol index

Use this page to find every published OpenTUI entry point and named export. The tables come from the generated publish maps and source barrels. They include runtime values and type-only exports.

The [API reference](/docs/api) lists the same exports with their signatures and the release that added each one. [Releases](/docs/releases) lists the API changes of each release.

## Support classes

| Class                                | Meaning                                                                                                             |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
| Supported                            | This is a normal supported API.                                                                                     |
| Advanced                             | This API needs direct ownership, lifecycle, rendering, or integration knowledge.                                    |
| Experimental                         | This API can change as its design develops.                                                                         |
| Runtime-specific                     | This API works only in the runtime shown in its row.                                                                |
| Deprecated                           | Keep this API only while you move to its replacement.                                                               |
| Implementation-looking public export | The export is publicly reachable today, but OpenTUI does not endorse it for ordinary use. It is still indexed here. |

A row can have more than one class. Read [Runtime and platform support](/docs/getting-started/runtime-support) for the supported Bun and Node.js versions.

## Entry point index

The runtime column describes the published target. A Node stub throws during import for each Bun-only support entry point.

| Package entry point                               | Runtime                               | Class                                           | Canonical documentation                                          |
| ------------------------------------------------- | ------------------------------------- | ----------------------------------------------- | ---------------------------------------------------------------- |
| `@opentui/core`                                   | Bun and Node.js                       | Supported                                       | [Renderer](/docs/core-concepts/renderer)                         |
| `@opentui/core/testing`                           | Bun and Node.js                       | Supported                                       | [Testing](/docs/core-concepts/testing)                           |
| `@opentui/core/runtime-plugin`                    | Bun only                              | Runtime-specific, Advanced                      | [Runtime module loading](/docs/extend/runtime-plugins)           |
| `@opentui/core/runtime-plugin-support`            | Bun only                              | Runtime-specific, Advanced                      | [Runtime module loading](/docs/extend/runtime-plugins)           |
| `@opentui/core/runtime-plugin-support/configure`  | Bun only                              | Runtime-specific, Advanced                      | [Runtime module loading](/docs/extend/runtime-plugins)           |
| `@opentui/core/yoga`                              | Bun and Node.js                       | Advanced                                        | [Yoga API](/docs/reference/yoga)                                 |
| `@opentui/core/tree-sitter/update-assets`         | Build time, with Bun CLI              | Runtime-specific                                | [Tree-sitter](/docs/reference/tree-sitter)                       |
| `@opentui/core/parser.worker`                     | Bun and Node.js worker                | Implementation-looking public export            | [Tree-sitter](/docs/reference/tree-sitter)                       |
| `@opentui/core/node-assets`                       | Node.js build time                    | Runtime-specific                                | [Standalone executables](/docs/reference/standalone-executables) |
| `@opentui/react`                                  | Bun and Node.js                       | Supported                                       | [React bindings](/docs/bindings/react)                           |
| `@opentui/react/renderer`                         | Bun and Node.js                       | Supported, Implementation-looking public export | [React bindings](/docs/bindings/react)                           |
| `@opentui/react/test-utils`                       | Bun and Node.js                       | Supported                                       | [Testing](/docs/core-concepts/testing)                           |
| `@opentui/react/runtime-plugin-support`           | Bun only                              | Runtime-specific, Advanced                      | [Runtime module loading](/docs/extend/runtime-plugins)           |
| `@opentui/react/runtime-plugin-support/configure` | Bun only                              | Runtime-specific, Advanced                      | [Runtime module loading](/docs/extend/runtime-plugins)           |
| `@opentui/react/jsx-runtime`                      | Compiler selected                     | Supported                                       | [React bindings](/docs/bindings/react)                           |
| `@opentui/react/jsx-dev-runtime`                  | Compiler selected                     | Supported                                       | [React bindings](/docs/bindings/react)                           |
| `@opentui/solid`                                  | Bun and Node.js                       | Supported                                       | [Solid bindings](/docs/bindings/solid)                           |
| `@opentui/solid/preload`                          | Bun only                              | Runtime-specific                                | [Solid bindings](/docs/bindings/solid)                           |
| `@opentui/solid/bun-plugin`                       | Bun only                              | Runtime-specific, Advanced                      | [Solid bindings](/docs/bindings/solid)                           |
| `@opentui/solid/runtime-plugin-support`           | Bun only                              | Runtime-specific, Advanced                      | [Runtime module loading](/docs/extend/runtime-plugins)           |
| `@opentui/solid/runtime-plugin-support/configure` | Bun only                              | Runtime-specific, Advanced                      | [Runtime module loading](/docs/extend/runtime-plugins)           |
| `@opentui/solid/components`                       | Bun and Node.js                       | Advanced                                        | [Solid bindings](/docs/bindings/solid)                           |
| `@opentui/solid/jsx-runtime`                      | Compiler selected                     | Supported                                       | [Solid bindings](/docs/bindings/solid)                           |
| `@opentui/solid/jsx-dev-runtime`                  | Compiler selected                     | Supported                                       | [Solid bindings](/docs/bindings/solid)                           |
| `@opentui/keymap`                                 | JavaScript runtimes                   | Supported                                       | [Core keymap API](/docs/keymap/core)                             |
| `@opentui/keymap/extras`                          | JavaScript runtimes                   | Supported                                       | [Core keymap API](/docs/keymap/core)                             |
| `@opentui/keymap/extras/graph`                    | JavaScript runtimes                   | Advanced                                        | [Core keymap API](/docs/keymap/core)                             |
| `@opentui/keymap/addons`                          | JavaScript runtimes                   | Supported                                       | [Built-in keymap addons](/docs/keymap/addons)                    |
| `@opentui/keymap/addons/opentui`                  | Bun and Node.js                       | Supported                                       | [Built-in keymap addons](/docs/keymap/addons)                    |
| `@opentui/keymap/testing`                         | JavaScript test runtimes              | Supported                                       | [Core keymap API](/docs/keymap/core)                             |
| `@opentui/keymap/html`                            | Browser DOM                           | Runtime-specific                                | [Keymap hosts](/docs/keymap/hosts)                               |
| `@opentui/keymap/opentui`                         | Bun and Node.js                       | Runtime-specific                                | [Keymap hosts](/docs/keymap/hosts)                               |
| `@opentui/keymap/react`                           | OpenTUI React                         | Runtime-specific                                | [React keymap integration](/docs/keymap/react)                   |
| `@opentui/keymap/solid`                           | OpenTUI Solid                         | Runtime-specific                                | [Solid keymap integration](/docs/keymap/solid)                   |
| `@opentui/keymap/runtime-modules`                 | Bun runtime loading                   | Runtime-specific, Advanced                      | [Runtime module loading](/docs/extend/runtime-plugins)           |
| `@opentui/qrcode`                                 | Bun and Node.js                       | Supported                                       | [QR encoder](/docs/reference/qr-encoder)                         |
| `@opentui/qrcode/react`                           | OpenTUI React                         | Runtime-specific                                | [QR code component](/docs/components/qr-code)                    |
| `@opentui/qrcode/solid`                           | OpenTUI Solid                         | Runtime-specific                                | [QR code component](/docs/components/qr-code)                    |
| `@opentui/ssh`                                    | Bun 1.3.14 or Node.js 26.4.0 or later | Runtime-specific                                | [SSH](/docs/reference/ssh)                                       |
| `@opentui/three`                                  | Bun only                              | Runtime-specific, Advanced                      | [Three.js WebGPU](/docs/reference/three)                         |
| `@opentui/three/runtime-modules`                  | Bun only                              | Runtime-specific, Advanced                      | [Runtime module loading](/docs/extend/runtime-plugins)           |

See [Package entry points](/docs/reference/package-entrypoints) for each entry point's role and import constraints.

## `@opentui/core`

The root works in Bun and supported Node.js releases. A class in these tables applies to every symbol in its row.

### Renderer and render tree

| Area                      | Symbols                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | Class                                | Canonical documentation                                                                                                                                      |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Renderer                  | `CliRenderer`, `CliRendererConfig`, `CliRendererErrorEvent`, `CliRendererExternalOutputEvent`, `CliRendererFrameEvent`, `CliRendererHandlerErrorEvent`, `CliRendererStats`, `CliRenderEvents`, `ConsoleMode`, `createCliRenderer`, `ExternalOutputMode`, `KittyKeyboardOptions`, `MouseButton`, `MouseEvent`, `PixelResolution`, `RendererControlState`, `ScreenMode`                                                                                                                                                                                  | Supported                            | [Renderer](/docs/core-concepts/renderer)                                                                                                                     |
| Scheduling and scrollback | `RendererSchedulerState`, `ScrollbackRenderContext`, `ScrollbackSnapshot`, `ScrollbackSurface`, `ScrollbackSurfaceCommitOptions`, `ScrollbackSurfaceOptions`, `ScrollbackWriter`, `SplitFooterReplayResetOptions`                                                                                                                                                                                                                                                                                                                                      | Advanced                             | [Renderer](/docs/core-concepts/renderer)                                                                                                                     |
| Shared renderer types     | `CapturedFrame`, `CapturedLine`, `CapturedSpan`, `CursorStyle`, `CursorStyleOptions`, `DebugOverlayCorner`, `ImageRenderProtocol`, `MousePointerStyle`, `RenderContext`, `RendererEvents`, `TargetChannel`, `TerminalCapabilities`, `TerminalCapabilityState`, `TerminalInfo`, `TerminalMultiplexer`, `ThemeMode`, `Timeout`, `ViewportBounds`, `WidthMethod`                                                                                                                                                                                          | Supported                            | [Renderer](/docs/core-concepts/renderer), [Interaction](/docs/core-concepts/interaction), and [Terminal capabilities](/docs/reference/terminal-capabilities) |
| Cell and highlight types  | `ATTRIBUTE_BASE_BITS`, `ATTRIBUTE_BASE_MASK`, `getBaseAttributes`, `Highlight`, `LineInfo`, `LineInfoProvider`, `TextAttributes`                                                                                                                                                                                                                                                                                                                                                                                                                       | Advanced                             | [Text and terminal cells](/docs/core-concepts/text-and-cells)                                                                                                |
| Renderables               | `BaseRenderable`, `BaseRenderableOptions`, `isRenderable`, `LayoutEvents`, `LayoutOptions`, `Position`, `Renderable`, `RenderableEvents`, `RenderableOptions`, `RootRenderable`                                                                                                                                                                                                                                                                                                                                                                        | Supported                            | [Renderables](/docs/core-concepts/renderables)                                                                                                               |
| Composition constructs    | `ASCIIFont`, `Box`, `Code`, `FrameBuffer`, `Generic`, `Input`, `ScrollBox`, `Select`, `TabSelect`, `Text`, `vstyles`                                                                                                                                                                                                                                                                                                                                                                                                                                   | Implementation-looking public export | [Renderables](/docs/core-concepts/renderables)                                                                                                               |
| Composition internals     | `Construct`, `DelegateMap`, `delegate`, `FunctionalConstruct`, `h`, `InstantiateFn`, `instantiate`, `isVNode`, `maybeMakeRenderable`, `PendingCall`, `ProxiedVNode`, `RenderableConstructor`, `ValidateShape`, `VChild`, `VNode`, `VRenderable`, `VRenderableOptions`, `wrapWithDelegates`                                                                                                                                                                                                                                                             | Implementation-looking public export | [Renderables](/docs/core-concepts/renderables)                                                                                                               |
| Layout string parsers     | `AlignString`, `BoxSizingString`, `DimensionString`, `DirectionString`, `DisplayString`, `EdgeString`, `FlexDirectionString`, `GutterString`, `JustifyString`, `LogLevelString`, `MeasureModeString`, `OverflowString`, `parseAlign`, `parseAlignItems`, `parseBoxSizing`, `parseDimension`, `parseDirection`, `parseDisplay`, `parseEdge`, `parseFlexDirection`, `parseGutter`, `parseJustify`, `parseLogLevel`, `parseMeasureMode`, `parseOverflow`, `parsePositionType`, `parseUnit`, `parseWrap`, `PositionTypeString`, `UnitString`, `WrapString` | Advanced                             | [Layout](/docs/core-concepts/layout)                                                                                                                         |
| Tree inspection           | `visualizeRenderableTree`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | Advanced                             | [Renderables](/docs/core-concepts/renderables)                                                                                                               |

### Input and selection

| Area                    | Symbols                                                                                                                                     | Class                                | Canonical documentation                                              |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | -------------------------------------------------------------------- |
| Keyboard events         | `KeyEvent`, `KeyHandler`, `KeyHandlerEventMap`, `PasteEvent`                                                                                | Supported                            | [Keyboard input](/docs/core-concepts/keyboard)                       |
| Keyboard implementation | `buildKittyKeyboardFlags`, `InternalKeyHandler`                                                                                             | Implementation-looking public export | [Keyboard input](/docs/core-concepts/keyboard)                       |
| Key parser              | `KeyEventType`, `nonAlphanumericKeys`, `ParsedKey`, `parseKeypress`, `ParseKeypressOptions`, `terminalNamedSingleStrokeKeys`                | Advanced                             | [Keyboard input](/docs/core-concepts/keyboard)                       |
| Raw input parser        | `StdinEvent`, `StdinParser`, `StdinParserOptions`, `StdinParserProtocolContext`, `StdinResponseProtocol`                                    | Advanced                             | [Keyboard input](/docs/core-concepts/keyboard)                       |
| Mouse parser            | `MouseEventType`, `MouseParser`, `RawMouseEvent`, `ScrollInfo`                                                                              | Advanced                             | [Interaction, focus, and selection](/docs/core-concepts/interaction) |
| Paste decoding          | `decodePasteBytes`, `PasteKind`, `PasteMetadata`, `stripAnsiSequences`                                                                      | Advanced                             | [Keyboard input](/docs/core-concepts/keyboard)                       |
| Scroll acceleration     | `LinearScrollAccel`, `MacOSScrollAccel`, `ScrollAcceleration`                                                                               | Advanced                             | [Interaction, focus, and selection](/docs/core-concepts/interaction) |
| Selection               | `ASCIIFontSelectionHelper`, `convertGlobalToLocalSelection`, `LocalSelectionBounds`, `Selection`, `SelectionBehavior`, `SelectionOccupancy` | Advanced                             | [Interaction, focus, and selection](/docs/core-concepts/interaction) |

### Text, color, links, and clipboard

| Area                 | Symbols                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | Class     | Canonical documentation                                       |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------- | ------------------------------------------------------------- |
| Styled text          | `bg`, `bgBlack`, `bgBlue`, `bgCyan`, `bgGreen`, `bgMagenta`, `bgRed`, `bgWhite`, `bgYellow`, `black`, `blink`, `blue`, `bold`, `brightBlack`, `brightBlue`, `brightCyan`, `brightGreen`, `brightMagenta`, `brightRed`, `brightWhite`, `brightYellow`, `Color`, `cyan`, `dim`, `fg`, `green`, `isStyledText`, `italic`, `link`, `magenta`, `red`, `reverse`, `strikethrough`, `stringToStyledText`, `StylableInput`, `StyleAttrs`, `StyledText`, `t`, `underline`, `white`, `yellow`                                                                                                                                                                                | Supported | [Text and terminal cells](/docs/core-concepts/text-and-cells) |
| Color values         | `ansi256IndexToRgb`, `ColorInput`, `ColorIntent`, `DEFAULT_BACKGROUND_RGB`, `DEFAULT_FOREGROUND_RGB`, `hexToRgb`, `hsvToRgb`, `normalizeColorValue`, `NormalizedColorValue`, `normalizeIndexedColorIndex`, `parseColor`, `RGBA`, `rgbToHex`, `RGBTriplet`                                                                                                                                                                                                                                                                                                                                                                                                          | Supported | [Colors](/docs/core-concepts/colors)                          |
| Attributes and links | `attributesWithLink`, `createTextAttributes`, `getLinkId`, `detectLinks`, `MAX_LINK_URL_BYTES`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | Advanced  | [Text and terminal cells](/docs/core-concepts/text-and-cells) |
| HAST conversion      | `HASTElement`, `HASTNode`, `HASTText`, `hastToStyledText`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          | Advanced  | [Text and terminal cells](/docs/core-concepts/text-and-cells) |
| Clipboard            | `ClipboardClearResult`, `ClipboardOptions`, `ClipboardReadOptions`, `ClipboardReadResult`, `ClipboardRepresentation`, `ClipboardSelection`, `ClipboardService`, `ClipboardTarget`, `ClipboardWriteDestination`, `ClipboardWriteOptions`, `ClipboardWriteResult`, `createClipboard`, `createHostClipboard`, `createRendererClipboardAdapter`, `HostClipboardBackend`, `HostClipboardClearResult`, `HostClipboardOperationOptions`, `HostClipboardOptions`, `HostClipboardReadOptions`, `HostClipboardService`, `HostClipboardWriteOptions`, `HostClipboardWriteResult`, `RendererClipboardBoundary`, `TerminalClipboardAdapter`, `TerminalClipboardOperationResult` | Supported | [Clipboard](/docs/core-concepts/clipboard)                    |

### Tree-sitter and syntax styles

| Area                    | Symbols                                                                                                                                                                     | Class     | Canonical documentation                    |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ------------------------------------------ |
| Client lifecycle        | `addDefaultParsers`, `destroyTreeSitterClient`, `getTreeSitterClient`, `TreeSitterClient`                                                                                   | Supported | [Tree-sitter](/docs/reference/tree-sitter) |
| File type lookup        | `basenameToFiletype`, `extensionToFiletype`, `extToFiletype`, `infoStringToFiletype`, `pathToFiletype`                                                                      | Supported | [Tree-sitter](/docs/reference/tree-sitter) |
| Styled output           | `treeSitterToStyledText`, `TreeSitterToStyledTextOptions`, `treeSitterToTextChunks`                                                                                         | Supported | [Tree-sitter](/docs/reference/tree-sitter) |
| Parser and result types | `FiletypeParserOptions`, `HighlightMeta`, `HighlightRange`, `HighlightResponse`, `InjectionMapping`, `SimpleHighlight`, `TreeSitterClientEvents`, `TreeSitterClientOptions` | Supported | [Tree-sitter](/docs/reference/tree-sitter) |
| Worker protocol         | `BufferState`, `Edit`, `ParsedBuffer`, `PerformanceStats`, `TreeSitterWorkerLogType`, `TreeSitterWorkerRequest`, `TreeSitterWorkerResponse`                                 | Advanced  | [Tree-sitter](/docs/reference/tree-sitter) |
| Syntax styles           | `convertThemeToStyles`, `MergedStyle`, `StyleDefinition`, `StyleDefinitionInput`, `SyntaxStyle`, `ThemeTokenStyle`                                                          | Supported | [Tree-sitter](/docs/reference/tree-sitter) |

### Plugins, console, and process services

| Area                        | Symbols                                                                                                                                                                                                                                                                                 | Class                                | Canonical documentation                                        |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | -------------------------------------------------------------- |
| Generic slots               | `createSlotRegistry`, `Plugin`, `PluginContext`, `PluginErrorEvent`, `PluginErrorPhase`, `PluginErrorReport`, `PluginErrorSource`, `ResolvedSlotRenderer`, `SlotMode`, `SlotRegistry`, `SlotRegistryOptions`, `SlotRenderer`                                                            | Supported                            | [Plugin slots](/docs/plugins/slots)                            |
| Core slots                  | `CoreManagedSlot`, `CorePlugin`, `CoreResolvedSlotRenderer`, `CoreSlotContribution`, `CoreSlotFailurePlaceholder`, `CoreSlotMode`, `CoreSlotRegistry`, `CoreSlotRenderer`, `createCoreSlotRegistry`, `registerCorePlugin`, `resolveCoreSlot`, `SlotRenderable`, `SlotRenderableOptions` | Supported                            | [Core plugin slots](/docs/plugins/core)                        |
| Console overlay             | `capture`, `ConsoleAction`, `ConsoleKeyBinding`, `ConsoleOptions`, `ConsolePosition`, `TerminalConsole`                                                                                                                                                                                 | Supported                            | [Console overlay](/docs/core-concepts/console)                 |
| Environment access          | `clearEnvCache`, `env`, `EnvVarConfig`, `registerEnvVar`                                                                                                                                                                                                                                | Advanced                             | [Environment variables](/docs/reference/env-vars)              |
| Environment registry output | `envRegistry`, `generateEnvColored`, `generateEnvMarkdown`                                                                                                                                                                                                                              | Implementation-looking public export | [Environment variables](/docs/reference/env-vars)              |
| Terminal palette            | `buildTerminalPaletteSignature`, `createTerminalPalette`, `GetPaletteOptions`, `NormalizedTerminalPalette`, `normalizeTerminalPalette`, `OscSubscriptionSource`, `TerminalColors`, `TerminalPalette`, `TerminalPaletteDetector`, `TerminalPaletteOptions`, `WriteFunction`              | Advanced                             | [Terminal capabilities](/docs/reference/terminal-capabilities) |
| Application paths           | `DataPaths`, `DataPathsEvents`, `DataPathsManager`, `getDataPaths`                                                                                                                                                                                                                      | Advanced                             | [Application data paths](/docs/reference/data-paths)           |
| Clocks                      | `Clock`, `SystemClock`, `TimerHandle`                                                                                                                                                                                                                                                   | Advanced                             | [Animation and Timeline](/docs/application-apis/animation)     |

### Components and component helpers

| Component or area          | Symbols                                                                                                                                                                                                                                                                           | Class                                | Canonical documentation                                       |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | ------------------------------------------------------------- |
| ASCII font                 | `ASCIIFontName`, `ASCIIFontOptions`, `ASCIIFontRenderable`, `coordinateToCharacterIndex`, `fonts`, `getCharacterPositions`, `measureText`, `renderFontToFrameBuffer`                                                                                                              | Supported                            | [ASCII font](/docs/components/ascii-font)                     |
| Box and borders            | `BorderCharacters`, `BorderCharArrays`, `BorderChars`, `borderCharsToArray`, `BorderConfig`, `BorderSides`, `BorderSidesConfig`, `BorderStyle`, `BoxDrawOptions`, `BoxOptions`, `BoxRenderable`, `getBorderFromSides`, `getBorderSides`, `isValidBorderStyle`, `parseBorderStyle` | Supported                            | [Box](/docs/components/box)                                   |
| Code                       | `ChunkRenderContext`, `CodeOptions`, `CodeRenderable`, `HighlightContext`, `OnChunksCallback`, `OnHighlightCallback`                                                                                                                                                              | Supported                            | [Code](/docs/components/code)                                 |
| Diff                       | `DiffRenderable`, `DiffRenderableOptions`                                                                                                                                                                                                                                         | Supported                            | [Diff](/docs/components/diff)                                 |
| Embedded terminal          | `EmbeddedTerminalDataSource`, `EmbeddedTerminalOptions`, `EmbeddedTerminalRenderable`, `EmbeddedTerminalScreen`                                                                                                                                                                   | Supported                            | [Embedded terminal](/docs/components/embedded-terminal)       |
| Editable buffer renderable | `ContentChangeEvent`, `CursorChangeEvent`, `EditBufferOptions`, `EditBufferRenderable`, `EditBufferRenderableEvents`, `EditorCapture`, `EditorTraits`, `isEditBufferRenderable`                                                                                                   | Advanced                             | [Editing buffers and views](/docs/extend/editing)             |
| Frame buffer               | `FrameBufferOptions`, `FrameBufferRenderable`                                                                                                                                                                                                                                     | Advanced                             | [Frame buffer](/docs/components/frame-buffer)                 |
| Image display              | `ImageFit`, `ImageRenderable`, `ImageRenderableOptions`, `ImageRenderableSource`, `resolveImageRenderProtocol`                                                                                                                                                                    | Supported                            | [Image](/docs/components/image)                               |
| Kitty transport            | `KittyImageTransport`, `KittyImageTransportStatus`                                                                                                                                                                                                                                | Supported                            | [Kitty transport](/docs/components/image#kitty-transport)     |
| Input                      | `InputAction`, `InputKeyBinding`, `InputRenderable`, `InputRenderableEvents`, `InputRenderableOptions`                                                                                                                                                                            | Supported                            | [Input](/docs/components/input)                               |
| Line number gutter         | `LineColorConfig`, `LineNumberOptions`, `LineNumberRenderable`, `LineSign`                                                                                                                                                                                                        | Supported                            | [Line number gutter](/docs/components/line-number)            |
| Markdown                   | `BlockState`, `createMarkdownCodeBlockRenderer`, `MarkdownCodeBlockRenderer`, `MarkdownCodeBlockRendererMap`, `MarkdownOptions`, `MarkdownRenderable`, `MarkdownTableOptions`, `MarkdownTableStyle`, `RenderNodeContext`                                                          | Supported                            | [Markdown](/docs/components/markdown)                         |
| Markdown parser state      | `ParseState`                                                                                                                                                                                                                                                                      | Implementation-looking public export | [Markdown](/docs/components/markdown)                         |
| Scroll bar                 | `ArrowOptions`, `ArrowRenderable`, `ScrollBarOptions`, `ScrollBarRenderable`, `ScrollUnit`                                                                                                                                                                                        | Advanced                             | [Scroll bar](/docs/components/scrollbar)                      |
| Scroll box                 | `ScrollBoxOptions`, `ScrollBoxRenderable`                                                                                                                                                                                                                                         | Supported                            | [Scroll box](/docs/components/scrollbox)                      |
| Select                     | `SelectAction`, `SelectKeyBinding`, `SelectOption`, `SelectRenderable`, `SelectRenderableEvents`, `SelectRenderableOptions`                                                                                                                                                       | Supported                            | [Select](/docs/components/select)                             |
| Slider                     | `SliderOptions`, `SliderRenderable`                                                                                                                                                                                                                                               | Supported                            | [Slider](/docs/components/slider)                             |
| Tab select                 | `TabSelectAction`, `TabSelectKeyBinding`, `TabSelectOption`, `TabSelectRenderable`, `TabSelectRenderableEvents`, `TabSelectRenderableOptions`                                                                                                                                     | Supported                            | [Tab select](/docs/components/tab-select)                     |
| Text                       | `TextOptions`, `TextRenderable`                                                                                                                                                                                                                                                   | Supported                            | [Text](/docs/components/text)                                 |
| Text nodes                 | `isTextNodeRenderable`, `RootTextNodeRenderable`, `TextNodeOptions`, `TextNodeRenderable`                                                                                                                                                                                         | Advanced                             | [Text and terminal cells](/docs/core-concepts/text-and-cells) |
| Text table                 | `TextTableCellContent`, `TextTableColumnFitter`, `TextTableColumnWidthMode`, `TextTableContent`, `TextTableOptions`, `TextTableRenderable`                                                                                                                                        | Supported                            | [Text table](/docs/components/text-table)                     |
| Textarea                   | `defaultTextareaKeyBindings`, `KeyBinding`, `SubmitEvent`, `TextareaAction`, `TextareaKeyAliasMap`, `TextareaOptions`, `TextareaRenderable`                                                                                                                                       | Supported                            | [Textarea](/docs/components/textarea)                         |
| Text buffer renderable     | `TextBufferOptions`, `TextBufferRenderable`                                                                                                                                                                                                                                       | Advanced                             | [Editing buffers and views](/docs/extend/editing)             |
| First-draw timestamp       | `TimeToFirstDrawOptions`, `TimeToFirstDrawRenderable`                                                                                                                                                                                                                             | Supported                            | [Time to first draw](/docs/components/time-to-first-draw)     |

### Buffers, editing, animation, and effects

| Area             | Symbols                                                                                                                                                                                                                                                                                                                        | Class                                | Canonical documentation                                    |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------ | ---------------------------------------------------------- |
| Cell buffer      | `OptimizedBuffer`                                                                                                                                                                                                                                                                                                              | Advanced                             | [Buffer API](/docs/reference/buffer-api)                   |
| Text buffer      | `TextBuffer`, `TextChunk`, `TextBufferView`                                                                                                                                                                                                                                                                                    | Advanced                             | [Editing buffers and views](/docs/extend/editing)          |
| Editing          | `EditBuffer`, `EditorView`, `measureEditText`, `Viewport`                                                                                                                                                                                                                                                                      | Advanced                             | [Editing buffers and views](/docs/extend/editing)          |
| Extmarks         | `createExtmarksController`, `Extmark`, `ExtmarkOptions`, `ExtmarksController`                                                                                                                                                                                                                                                  | Experimental, Advanced               | [Editing buffers and views](/docs/extend/editing)          |
| Timeline         | `AnimationOptions`, `createTimeline`, `getTimelineEngine`, `EasingFunctions`, `JSAnimation`, `Timeline`, `TimelineOptions`                                                                                                                                                                                                     | Supported                            | [Animation and Timeline](/docs/application-apis/animation) |
| Animation engine | `engine`                                                                                                                                                                                                                                                                                                                       | Implementation-looking public export | [Animation and Timeline](/docs/application-apis/animation) |
| Effects          | `CloudsEffect`, `CRTRollingBarEffect`, `DistortionEffect`, `FlamesEffect`, `RainbowTextEffect`, `VignetteEffect`                                                                                                                                                                                                               | Experimental, Advanced               | [Post-processing effects](/docs/extend/post-processing)    |
| Filters          | `applyAsciiArt`, `applyBrightness`, `applyChromaticAberration`, `applyGain`, `applyInvert`, `applyNoise`, `applySaturation`, `applyScanlines`, `BloomEffect`                                                                                                                                                                   | Experimental, Advanced               | [Post-processing effects](/docs/extend/post-processing)    |
| Matrices         | `ACHROMATOPSIA_MATRIX`, `DEUTERANOPIA_COMP_MATRIX`, `DEUTERANOPIA_SIM_MATRIX`, `GRAYSCALE_MATRIX`, `GREENSCALE_MATRIX`, `INVERT_MATRIX`, `PROTANOPIA_COMP_MATRIX`, `PROTANOPIA_SIM_MATRIX`, `SEPIA_MATRIX`, `SOLARIZATION_MATRIX`, `SYNTHWAVE_MATRIX`, `TECHNICOLOR_MATRIX`, `TRITANOPIA_COMP_MATRIX`, `TRITANOPIA_SIM_MATRIX` | Advanced                             | [Color matrices](/docs/reference/color-matrix)             |

### Images, audio, and native span output

| Area                        | Symbols                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | Class     | Canonical documentation                                             |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------- |
| `NativeImage` API           | `BlendMode`, `CompositeOptions`, `ExtendOptions`, `ExtractOptions`, `ImageColorStatus`, `ImageError`, `ImageErrorCode`, `ImageFormat`, `imageInfo`, `ImageInfo`, `ImageLoadError`, `ImageLoadErrorCode`, `ImageLoadOptions`, `ImageSource`, `NativeImage`, `NativeImagePool`, `NativeImagePoolOptions`, `OwnedRawImage`, `PixelFormat`, `PixelImportOptions`, `RawImage`, `ResizeKernel`, `ResizeOptions`                                                                                                                                                                                                                                                                                                                                                                                                | Advanced  | [`NativeImage`](/docs/reference/native-image)                       |
| Audio playback and mixing   | `Audio`, `AudioAction`, `AudioErrorContext`, `AudioEvents`, `AudioGroup`, `AudioInitializationAction`, `AudioInitializationError`, `AudioPlaybackDevice`, `AudioPlayOptions`, `AudioSetupOptions`, `AudioSound`, `AudioStartOptions`, `AudioVoice`, `setupAudio`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | Supported | [Audio](/docs/core-concepts/audio)                                  |
| Streaming audio             | `AudioStream`, `AudioStreamAction`, `AudioStreamBody`, `AudioStreamBodyOptions`, `AudioStreamBufferOptions`, `AudioStreamConnectContext`, `AudioStreamConnection`, `AudioStreamConnector`, `AudioStreamContentTypeContext`, `AudioStreamContentTypePolicy`, `AudioStreamDemuxer`, `AudioStreamDemuxerFactory`, `AudioStreamDemuxOutput`, `AudioStreamError`, `AudioStreamErrorContext`, `AudioStreamEvents`, `AudioStreamFormat`, `AudioStreamMetadata`, `AudioStreamMetadataFormat`, `AudioStreamOptions`, `AudioStreamReconnectEvent`, `AudioStreamReconnectOptions`, `AudioStreamRetryContext`, `AudioStreamRetryDecision`, `AudioStreamRetryPhase`, `AudioStreamSourceOptions`, `AudioStreamState`, `AudioStreamStats`, `AudioStreamUrlOptions`, `createIcyStreamDemuxer`, `IcyStreamDemuxerOptions` | Supported | [Streaming audio](/docs/application-apis/audio-streaming)           |
| Audio capture and recording | `AudioCaptureDevice`, `AudioCaptureOptions`, `AudioCaptureReadResult`, `AudioCaptureStats`, `AudioCaptureStream`, `AudioCaptureStreamAction`, `AudioCaptureStreamError`, `AudioCaptureStreamErrorContext`, `AudioCaptureStreamEvents`, `AudioCaptureStreamOptions`, `AudioCaptureStreamState`, `AudioCaptureStreamStats`, `AudioRecorder`, `AudioRecorderAction`, `AudioRecorderError`, `AudioRecorderErrorContext`, `AudioRecorderEvents`, `AudioRecorderState`, `AudioRecorderStats`, `AudioRecordToFileOptions`                                                                                                                                                                                                                                                                                       | Supported | [Audio capture and recording](/docs/application-apis/audio-capture) |
| Native span output          | `DataHandler`, `NativeSpanFeed`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          | Advanced  | [`NativeSpanFeed`](/docs/reference/native-span-feed)                |

### Low-level native binding exports

These names expose the TypeScript side of the native binding. Use the higher-level API unless a low-level guide requires one of them.

Start with [Native rendering](/docs/native/overview) for the scene model.
The [C](/docs/native/c) and [Zig](/docs/native/zig) guides cover their native interfaces, which are separate from npm entry points.

| Area                   | Symbols                                                                    | Class    | Canonical documentation                                                          |
| ---------------------- | -------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------- |
| Standalone ownership   | `ResourceContext`, `NativeResourceOwner`, `BufferAccess`, `EncodedUnicode` | Advanced | [Core resource ownership](/docs/native/core#resources-through-core)              |
| Session driver         | `NativeSession`, `NativeSessionDriverOptions`, `NativeSessionScheduler`    | Advanced | [How Core uses native](/docs/native/core)                                        |
| Renderable integration | `NativeRenderableIntegration`                                              | Advanced | [Custom renderables](/docs/extend/custom-renderables#declare-native-integration) |
| Image ownership        | `ImageCreateOptions`                                                       | Advanced | [NativeImage ownership](/docs/reference/native-image#choose-a-context-owner)     |
| Checked errors         | `NativeError`, `NativeStatus`                                              | Advanced | [C results](/docs/native/c#initialize-records-and-check-results)                 |

| Area                             | Symbols                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | Class                                | Canonical documentation                                          |
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | ---------------------------------------------------------------- |
| Native handles and binding types | `AudioEngineHandle`, `AudioEngineLib`, `ClipboardOperationHandle`, `ClipboardServiceHandle`, `ContextBufferHandle`, `ContextEditBufferHandle`, `ContextEditorViewHandle`, `ContextEmbeddedTerminalHandle`, `ContextImageHandle`, `ContextImagePixelsHandle`, `ContextObjectHandle`, `ContextSyntaxStyleHandle`, `ContextTextBufferHandle`, `ContextTextBufferViewHandle`, `ContextUnicodeHandle`, `CursorState`, `EmbeddedTerminalCursor`, `EmbeddedTerminalKey`, `EmbeddedTerminalMouse`, `FFIRenderLib`, `ImageHandle`, `LogicalCursor`, `LogLevel`, `MeasureResult`, `NativeAudioStreamCloseReason`, `NativeAudioStreamFormat`, `NativeAudioStreamState`, `NativeBufferedOutput`, `NativeClipboardCancelStatus`, `NativeClipboardCopyStatus`, `NativeClipboardDestroyStatus`, `NativeClipboardOperationStatus`, `NativeClipboardShutdownStatus`, `NativeClipboardStartStatus`, `NativeContextHandle`, `NativeHandle`, `NativeSpanFeedEventHandler`, `NativeYogaDirtiedCallback`, `NativeYogaLayout`, `NativeYogaMeasureCallback`, `RenderLib`, `resolveRenderLib`, `SceneNodeHandle`, `SessionHandle`, `setRenderLibPath`, `VisualCursor` | Implementation-looking public export | [This section](#low-level-native-binding-exports)                |
| Context and buffer bindings      | `NATIVE_BUFFER_TEXT_BYTES_MAX`, `NativeBufferDraw`, `NativeBufferGrid`, `NativeBufferStack`, `NativeContextBufferLease`, `NativeContextBufferOptions`, `NativeContextImageDraw`, `NativeContextOptions`, `NativeDrawingTarget`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | Implementation-looking public export | [This section](#low-level-native-binding-exports)                |
| Scene bindings                   | `NATIVE_EDGE_NONE`, `NativeBorder`, `NativeSceneArrowOptions`, `NativeSceneBoxDetails`, `NativeSceneEditorOptions`, `NativeSceneFrame`, `NativeSceneFrameOptions`, `NativeSceneFrameRequest`, `NativeSceneHook`, `NativeSceneLayout`, `NativeScenePaint`, `NativeScenePaintPhase`, `NativeScenePaintSlot`, `NativeScenePaintUpdate`, `NativeSceneSliderOptions`, `NativeSceneTextMetrics`, `NativeSceneTextOptions`, `NativeSceneTextSelectionOptions`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | Implementation-looking public export | [This section](#low-level-native-binding-exports)                |
| Session bindings                 | `acquireSessionBufferLease`, `NATIVE_SESSION_CONTROL_PACKET_BYTES`, `NativeOutputTicket`, `NativeSessionBufferLease`, `NativeSessionCapabilities`, `NativeSessionControl`, `NativeSessionCursorOptions`, `NativeSessionKittyImageTransportStatus`, `NativeSessionOptions`, `NativeSessionPumpResult`, `NativeSessionPumpStatus`, `NativeSessionRendererOptions`, `NativeSessionRendererState`, `NativeSessionRenderStatus`, `NativeSessionSplitControl`, `NativeSessionState`, `NativeSessionTerminalOptions`, `NativeSessionTerminalPhase`, `NativeSplitSnapshot`, `SessionBuffer`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          | Implementation-looking public export | [This section](#low-level-native-binding-exports)                |
| Text and editor bindings         | `NativeContextEditBufferOptions`, `NativeEditBufferInfo`, `NativeEditCommand`, `NativeEditEvent`, `NativeEditEventName`, `NativeEditHighlightOperation`, `NativeEditorCommand`, `NativeEditorPositionQuery`, `NativeEditorReplacement`, `NativeEditorSelection`, `NativeEditorSelectionOperation`, `NativeEditorStyle`, `NativeEditorStyleMask`, `NativeEditorViewInfo`, `NativeEditorViewport`, `NativeEditPositionQuery`, `NativeEncodedStyledText`, `NativeTextBufferInfo`, `NativeTextViewCommand`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | Implementation-looking public export | [This section](#low-level-native-binding-exports)                |
| Native result structures         | `AllocatorStats`, `AudioStreamCreateOptions`, `BuildOptions`, `GrowthPolicy`, `NativeAudioCaptureStats`, `NativeAudioStreamStats`, `NativeRenderStats`, `NativeSpanFeedOptions`, `NativeSpanFeedStats`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | Advanced                             | [This section](#low-level-native-binding-exports)                |
| Bundled asset resolution         | `resolveBundledFilePath`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | Implementation-looking public export | [Standalone executables](/docs/reference/standalone-executables) |
| Yoga namespace                   | `Yoga`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | Advanced                             | [Yoga API](/docs/reference/yoga)                                 |

## Other `@opentui/core` entry points

### Testing

`@opentui/core/testing` works in Bun and Node.js.

| Area                  | Symbols                                                                                                                                                                                                                         | Class     | Canonical documentation                |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -------------------------------------- |
| Renderer setup        | `createTestRenderer`, `MockInput`, `MockMouse`, `TestExternalOutput`, `TestExternalOutputCommit`, `TestFlushOptions`, `TestRenderer`, `TestRendererOptions`, `TestRendererSetup`, `TestVisualIdleOptions`, `TestWaitForOptions` | Supported | [Testing](/docs/core-concepts/testing) |
| Keyboard and mouse    | `createMockKeys`, `createMockMouse`, `KeyCodes`, `KeyInput`, `MockKeysOptions`, `MouseButton`, `MouseButtons`, `MouseEventOptions`, `MouseEventType`, `MouseModifiers`, `MousePosition`, `pasteBytes`                           | Supported | [Testing](/docs/core-concepts/testing) |
| Capabilities          | `createTerminalCapabilities`, `setRendererCapabilities`, `TerminalCapabilitiesOverrides`                                                                                                                                        | Supported | [Testing](/docs/core-concepts/testing) |
| Deterministic helpers | `createSpy`, `ManualClock`, `MockTreeSitterClient`                                                                                                                                                                              | Supported | [Testing](/docs/core-concepts/testing) |
| Recording             | `RecordedFrame`, `TestRecorder`                                                                                                                                                                                                 | Supported | [Testing](/docs/core-concepts/testing) |

### Runtime plugin support

These entry points are Bun-only. Their Node.js targets are stubs that throw during import.

| Package entry point                              | Symbols                                                                                                                                                                                                                                            | Class                      | Canonical documentation                                |
| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------ |
| `@opentui/core/runtime-plugin`                   | `createRuntimePlugin`, `CreateRuntimePluginOptions`, `isCoreRuntimeModuleSpecifier`, `RuntimeModuleEntry`, `RuntimeModuleExports`, `runtimeModuleIdForSpecifier`, `RuntimeModuleLoader`, `RuntimePluginRewriteOptions`, `RuntimeSpecifierPreserve` | Runtime-specific, Advanced | [Runtime module loading](/docs/extend/runtime-plugins) |
| `@opentui/core/runtime-plugin-support`           | `createRuntimePlugin`, `CreateRuntimePluginOptions`, `ensureRuntimePluginSupport`, `RuntimeModuleEntry`, `RuntimeModuleExports`, `runtimeModuleIdForSpecifier`, `RuntimeModuleLoader`, `RuntimeSpecifierPreserve`                                  | Runtime-specific, Advanced | [Runtime module loading](/docs/extend/runtime-plugins) |
| `@opentui/core/runtime-plugin-support/configure` | `createRuntimePlugin`, `CreateRuntimePluginOptions`, `ensureRuntimePluginSupport`, `RuntimeModuleEntry`, `RuntimeModuleExports`, `runtimeModuleIdForSpecifier`, `RuntimeModuleLoader`, `RuntimeSpecifierPreserve`                                  | Runtime-specific, Advanced | [Runtime module loading](/docs/extend/runtime-plugins) |

### Yoga

`@opentui/core/yoga` works in Bun and Node.js. The root `Yoga` namespace exposes this module. See [Yoga API](/docs/reference/yoga) for methods and ownership.

| Purpose                    | Symbols                                                                                                                                                                                        | Class                  |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- |
| Objects and callback types | `Config`, `Node`, `default`, `DirtiedFunction`, `Layout`, `MeasureFunction`, `Size`, `Value`                                                                                                   | Advanced               |
| Errors                     | `YogaError`, `YogaStatus`                                                                                                                                                                      | Advanced               |
| Alignment                  | `Align`, `ALIGN_AUTO`, `ALIGN_BASELINE`, `ALIGN_CENTER`, `ALIGN_FLEX_END`, `ALIGN_FLEX_START`, `ALIGN_SPACE_AROUND`, `ALIGN_SPACE_BETWEEN`, `ALIGN_SPACE_EVENLY`, `ALIGN_STRETCH`              | Advanced               |
| Box sizing                 | `BoxSizing`, `BOX_SIZING_BORDER_BOX`, `BOX_SIZING_CONTENT_BOX`                                                                                                                                 | Advanced               |
| Dimensions                 | `Dimension`, `DIMENSION_HEIGHT`, `DIMENSION_WIDTH`                                                                                                                                             | Advanced               |
| Direction                  | `Direction`, `DIRECTION_INHERIT`, `DIRECTION_LTR`, `DIRECTION_RTL`                                                                                                                             | Advanced               |
| Display                    | `Display`, `DISPLAY_CONTENTS`, `DISPLAY_FLEX`, `DISPLAY_NONE`                                                                                                                                  | Advanced               |
| Edges                      | `Edge`, `EDGE_ALL`, `EDGE_BOTTOM`, `EDGE_END`, `EDGE_HORIZONTAL`, `EDGE_LEFT`, `EDGE_RIGHT`, `EDGE_START`, `EDGE_TOP`, `EDGE_VERTICAL`                                                         | Advanced               |
| Compatibility errata       | `Errata`, `ERRATA_ABSOLUTE_PERCENT_AGAINST_INNER_SIZE`, `ERRATA_ABSOLUTE_POSITION_WITHOUT_INSETS_EXCLUDES_PADDING`, `ERRATA_ALL`, `ERRATA_CLASSIC`, `ERRATA_NONE`, `ERRATA_STRETCH_FLEX_BASIS` | Advanced               |
| Experimental feature       | `ExperimentalFeature`, `EXPERIMENTAL_FEATURE_WEB_FLEX_BASIS`                                                                                                                                   | Experimental, Advanced |
| Flex direction             | `FlexDirection`, `FLEX_DIRECTION_COLUMN`, `FLEX_DIRECTION_COLUMN_REVERSE`, `FLEX_DIRECTION_ROW`, `FLEX_DIRECTION_ROW_REVERSE`                                                                  | Advanced               |
| Gaps                       | `Gutter`, `GUTTER_ALL`, `GUTTER_COLUMN`, `GUTTER_ROW`                                                                                                                                          | Advanced               |
| Justification              | `Justify`, `JUSTIFY_CENTER`, `JUSTIFY_FLEX_END`, `JUSTIFY_FLEX_START`, `JUSTIFY_SPACE_AROUND`, `JUSTIFY_SPACE_BETWEEN`, `JUSTIFY_SPACE_EVENLY`                                                 | Advanced               |
| Logging                    | `LogLevel`, `LOG_LEVEL_DEBUG`, `LOG_LEVEL_ERROR`, `LOG_LEVEL_FATAL`, `LOG_LEVEL_INFO`, `LOG_LEVEL_VERBOSE`, `LOG_LEVEL_WARN`                                                                   | Advanced               |
| Measurement                | `MeasureMode`, `MEASURE_MODE_AT_MOST`, `MEASURE_MODE_EXACTLY`, `MEASURE_MODE_UNDEFINED`                                                                                                        | Advanced               |
| Node type                  | `NodeType`, `NODE_TYPE_DEFAULT`, `NODE_TYPE_TEXT`                                                                                                                                              | Advanced               |
| Overflow                   | `Overflow`, `OVERFLOW_HIDDEN`, `OVERFLOW_SCROLL`, `OVERFLOW_VISIBLE`                                                                                                                           | Advanced               |
| Position                   | `PositionType`, `POSITION_TYPE_ABSOLUTE`, `POSITION_TYPE_RELATIVE`, `POSITION_TYPE_STATIC`                                                                                                     | Advanced               |
| Units                      | `Unit`, `UNIT_AUTO`, `UNIT_PERCENT`, `UNIT_POINT`, `UNIT_UNDEFINED`                                                                                                                            | Advanced               |
| Wrapping                   | `Wrap`, `WRAP_NO_WRAP`, `WRAP_WRAP`, `WRAP_WRAP_REVERSE`                                                                                                                                       | Advanced               |

### Tree-sitter assets, worker, and Node assets

| Package entry point                       | Runtime                  | Symbols                                               | Class                                | Canonical documentation                                          |
| ----------------------------------------- | ------------------------ | ----------------------------------------------------- | ------------------------------------ | ---------------------------------------------------------------- |
| `@opentui/core/tree-sitter/update-assets` | Build time, with Bun CLI | `runUpdateAssetsCli`, `updateAssets`, `UpdateOptions` | Runtime-specific                     | [Tree-sitter](/docs/reference/tree-sitter)                       |
| `@opentui/core/parser.worker`             | Bun and Node.js worker   | No named application exports                          | Implementation-looking public export | [Tree-sitter](/docs/reference/tree-sitter)                       |
| `@opentui/core/node-assets`               | Node.js build time       | `getNodeAssets`, `NodeAsset`, `NodeAssetTarget`       | Runtime-specific                     | [Standalone executables](/docs/reference/standalone-executables) |

## `@opentui/react`

The root and compiler-selected JSX entry points work in Bun and Node.js.

### Root exports

| Area                 | Symbols                                                                                                                                                                                                                                                                                                       | Class                                | Canonical documentation                                   |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | --------------------------------------------------------- |
| Root rendering       | `createElement`, `createPortal`, `createRoot`, `flushSync`, `Root`                                                                                                                                                                                                                                            | Supported                            | [React bindings](/docs/bindings/react)                    |
| Application context  | `AppContext`, `useAppContext`                                                                                                                                                                                                                                                                                 | Supported                            | [React bindings](/docs/bindings/react)                    |
| Hooks                | `useBlur`, `useFocus`, `useKeyboard`, `UseKeyboardOptions`, `useOnResize`, `usePaste`, `useRenderer`, `useSelectionHandler`, `useTerminalDimensions`, `useTimeline`                                                                                                                                           | Supported                            | [React bindings](/docs/bindings/react)                    |
| Catalogue extension  | `extend`, `ExtendedComponentProps`, `ExtendedIntrinsicElements`, `getComponentCatalogue`, `OpenTUIComponents`, `RenderableConstructor`                                                                                                                                                                        | Advanced                             | [React bindings](/docs/bindings/react)                    |
| Catalogue objects    | `baseComponents`, `componentCatalogue`                                                                                                                                                                                                                                                                        | Implementation-looking public export | [React bindings](/docs/bindings/react)                    |
| Component prop types | `AsciiFontProps`, `BoxProps`, `CodeProps`, `DiffProps`, `GetNonStyledProperties`, `ImageProps`, `InputProps`, `LineBreakProps`, `LineNumberProps`, `LinkProps`, `MarkdownProps`, `NonStyledProps`, `ReactProps`, `ScrollBoxProps`, `SelectProps`, `SpanProps`, `TabSelectProps`, `TextareaProps`, `TextProps` | Supported                            | [Components overview](/docs/components)                   |
| Plugin slots         | `createReactSlotRegistry`, `createSlot`, `ReactBoundSlotProps`, `ReactPlugin`, `ReactRegistrySlotComponent`, `ReactSlotComponent`, `ReactSlotOptions`, `ReactSlotProps`, `Slot`, `SlotMode`                                                                                                                   | Supported                            | [React plugin slots](/docs/plugins/react)                 |
| First-draw timestamp | `TimeToFirstDraw`, `TimeToFirstDrawProps`                                                                                                                                                                                                                                                                     | Supported                            | [Time to first draw](/docs/components/time-to-first-draw) |

### Other React entry points

| Package entry point                               | Symbols                                                          | Class                                | Canonical documentation                                |
| ------------------------------------------------- | ---------------------------------------------------------------- | ------------------------------------ | ------------------------------------------------------ |
| `@opentui/react/renderer` declarations            | `createPortal`, `createRoot`, `flushSync`, `Root`                | Supported                            | [React bindings](/docs/bindings/react)                 |
| `@opentui/react/renderer` runtime                 | All runtime values from `@opentui/react`                         | Implementation-looking public export | [React bindings](/docs/bindings/react)                 |
| `@opentui/react/test-utils`                       | `testRender`                                                     | Supported                            | [Testing](/docs/core-concepts/testing)                 |
| `@opentui/react/runtime-plugin-support`           | `ensureRuntimePluginSupport`, `ReactRuntimePluginSupportOptions` | Runtime-specific, Advanced           | [Runtime module loading](/docs/extend/runtime-plugins) |
| `@opentui/react/runtime-plugin-support/configure` | `ensureRuntimePluginSupport`, `ReactRuntimePluginSupportOptions` | Runtime-specific, Advanced           | [Runtime module loading](/docs/extend/runtime-plugins) |
| `@opentui/react/jsx-runtime`                      | `Fragment`, `jsx`, `jsxs`, `JSX`                                 | Supported                            | [React bindings](/docs/bindings/react)                 |
| `@opentui/react/jsx-dev-runtime`                  | `Fragment`, `jsxDEV`, `JSX`                                      | Supported                            | [React bindings](/docs/bindings/react)                 |

The generated React map points `/renderer` runtime imports to `index.js`. Its declaration path points to `reconciler/renderer.d.ts`. Treat root values outside the four declared names as a build leak.

## `@opentui/solid`

The root, `/components`, and compiler-selected JSX entry points work in Bun and Node.js.

### Root exports

| Area                            | Symbols                                                                                                                                                                                                                                                       | Class                                | Canonical documentation                                      |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | ------------------------------------------------------------ |
| Root rendering                  | `render`, `testRender`                                                                                                                                                                                                                                        | Supported                            | [Solid bindings](/docs/bindings/solid)                       |
| Hooks                           | `onBlur`, `onFocus`, `onResize`, `useKeyboard`, `UseKeyboardOptions`, `usePaste`, `useRenderer`, `useSelectionHandler`, `useTerminalDimensions`, `useTimeline`                                                                                                | Supported                            | [Solid bindings](/docs/bindings/solid)                       |
| Renamed hook                    | `useKeyHandler`                                                                                                                                                                                                                                               | Deprecated                           | Use `useKeyboard` in [Solid bindings](/docs/bindings/solid). |
| Components                      | `createDynamic`, `Dynamic`, `DynamicProps`, `Portal`                                                                                                                                                                                                          | Supported                            | [Solid bindings](/docs/bindings/solid)                       |
| Scrollback                      | `createScrollbackWriter`, `SolidScrollbackNode`, `SolidScrollbackWriterOptions`, `writeSolidToScrollback`                                                                                                                                                     | Advanced                             | [Renderer](/docs/core-concepts/renderer)                     |
| Catalogue extension             | `extend`, `ExtendedComponentProps`, `ExtendedIntrinsicElements`, `getComponentCatalogue`, `OpenTUIComponents`, `RenderableConstructor`                                                                                                                        | Advanced                             | [Solid bindings](/docs/bindings/solid)                       |
| Catalogue and text wrappers     | `baseComponents`, `BoldSpanRenderable`, `componentCatalogue`, `ItalicSpanRenderable`, `LineBreakRenderable`, `LinkOptions`, `LinkRenderable`, `TextNodeKey`, `textNodeKeys`, `UnderlineSpanRenderable`                                                        | Implementation-looking public export | [Solid bindings](/docs/bindings/solid)                       |
| Reconciler                      | `_render`, `createComponent`, `createElement`, `createSlotNode`, `createTextNode`, `DomNode`, `effect`, `insert`, `insertNode`, `memo`, `mergeProps`, `removeNode`, `setProp`, `spread`, `use`                                                                | Implementation-looking public export | [Solid bindings](/docs/bindings/solid)                       |
| Reconciler context and wrappers | `LayoutSlotRenderable`, `RenderableContext`, `RendererContext`, `SlotRenderable`, `TextSlotRenderable`                                                                                                                                                        | Implementation-looking public export | [Solid bindings](/docs/bindings/solid)                       |
| Component prop types            | `AsciiFontProps`, `BoxProps`, `CodeProps`, `ElementProps`, `GetNonStyledProperties`, `ImageProps`, `InputProps`, `LinkProps`, `MarkdownProps`, `NonStyledProps`, `ScrollBoxProps`, `SelectProps`, `SpanProps`, `TabSelectProps`, `TextareaProps`, `TextProps` | Supported                            | [Components overview](/docs/components)                      |
| Plugin slots                    | `createSlot`, `createSolidSlotRegistry`, `Slot`, `SlotMode`, `SolidBoundSlotProps`, `SolidPlugin`, `SolidRegistrySlotComponent`, `SolidSlotComponent`, `SolidSlotOptions`, `SolidSlotProps`                                                                   | Supported                            | [Solid plugin slots](/docs/plugins/solid)                    |
| JSX namespace                   | `JSX`                                                                                                                                                                                                                                                         | Supported                            | [Solid bindings](/docs/bindings/solid)                       |
| First-draw timestamp            | `TimeToFirstDraw`, `TimeToFirstDrawProps`                                                                                                                                                                                                                     | Supported                            | [Time to first draw](/docs/components/time-to-first-draw)    |

### Other Solid entry points

| Package entry point                               | Symbols                                                                                                                                      | Class                      | Canonical documentation                                |
| ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------ |
| `@opentui/solid/preload`                          | No named exports. Importing it installs the Bun source transform.                                                                            | Runtime-specific           | [Solid bindings](/docs/bindings/solid)                 |
| `@opentui/solid/bun-plugin`                       | `createSolidTransformPlugin`, `CreateSolidTransformPluginOptions`, `default`, `ensureSolidTransformPlugin`, `resetSolidTransformPluginState` | Runtime-specific, Advanced | [Solid bindings](/docs/bindings/solid)                 |
| `@opentui/solid/runtime-plugin-support`           | `ensureRuntimePluginSupport`, `SolidRuntimePluginSupportOptions`                                                                             | Runtime-specific, Advanced | [Runtime module loading](/docs/extend/runtime-plugins) |
| `@opentui/solid/runtime-plugin-support/configure` | `ensureRuntimePluginSupport`, `SolidRuntimePluginSupportOptions`                                                                             | Runtime-specific, Advanced | [Runtime module loading](/docs/extend/runtime-plugins) |
| `@opentui/solid/components`                       | `extend`, `ExtendedComponentProps`, `ExtendedIntrinsicElements`, `getComponentCatalogue`, `OpenTUIComponents`                                | Advanced                   | [Solid bindings](/docs/bindings/solid)                 |
| `@opentui/solid/jsx-runtime`                      | `Fragment`, `jsx`, `jsxDEV`, `jsxs`, `JSX`                                                                                                   | Supported                  | [Solid bindings](/docs/bindings/solid)                 |
| `@opentui/solid/jsx-dev-runtime`                  | `Fragment`, `jsxDEV`, `JSX`                                                                                                                  | Supported                  | [Solid bindings](/docs/bindings/solid)                 |

## `@opentui/keymap`

### Root engine

The root engine is host-agnostic. Use an adapter entry point to connect it to a browser or OpenTUI renderer.

| Kind   | Symbols                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          | Class     | Canonical documentation              |
| ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ------------------------------------ |
| Values | `Keymap`, `KEYMAP_EXTENSION_CONTEXT`, `stringifyKeySequence`, `stringifyKeyStroke`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | Supported | [Core keymap API](/docs/keymap/core) |
| Types  | `ActiveBinding`, `ActiveKey`, `ActiveKeyOptions`, `Attributes`, `Binding`, `BindingCommand`, `BindingEvent`, `BindingExpander`, `BindingExpanderContext`, `BindingExpansion`, `BindingFieldCompiler`, `BindingFieldContext`, `BindingParser`, `BindingParserContext`, `BindingParserResult`, `Bindings`, `BindingsValidationResult`, `BindingTransformer`, `BindingTransformerContext`, `Command`, `CommandBindingsQuery`, `CommandContext`, `CommandEntry`, `CommandFieldCompiler`, `CommandFieldContext`, `CommandFilter`, `CommandHandler`, `CommandQuery`, `CommandQueryValue`, `CommandResolver`, `CommandResolverContext`, `CommandResult`, `CommandTransformer`, `CommandTransformerContext`, `DispatchBinding`, `DispatchEvent`, `DispatchLayer`, `DispatchPhase`, `ErrorEvent`, `EventData`, `EventMatchResolver`, `EventMatchResolverContext`, `EventName`, `Events`, `HostCapability`, `HostMetadata`, `HostModifier`, `HostPlatform`, `InterceptName`, `Intercepts`, `KeyAfterInputContext`, `KeyAfterReason`, `KeyDeferredDisambiguationContext`, `KeyDeferredDisambiguationDecision`, `KeyDeferredDisambiguationHandler`, `KeyDisambiguationContext`, `KeyDisambiguationDecision`, `KeyDisambiguationResolver`, `KeyInputContext`, `KeyInterceptOptions`, `KeyLike`, `KeymapEvent`, `KeymapExtensionContext`, `KeymapExtensionProvider`, `KeymapHost`, `KeyMatch`, `KeySequencePart`, `KeyStringifyInput`, `KeyStrokeInput`, `KeyToken`, `Layer`, `LayerAnalysisContext`, `LayerAnalyzer`, `LayerBindingAnalysis`, `LayerBindingsTransformer`, `LayerBindingsTransformerContext`, `LayerFieldCompiler`, `LayerFieldContext`, `Listener`, `NormalizedKeyStroke`, `ParsedBinding`, `ParsedCommand`, `RawInputContext`, `RawInterceptOptions`, `ReactiveMatcher`, `ResolvedKeyToken`, `ResolvedSequencePattern`, `RunCommandOptions`, `RunCommandResult`, `SequencePattern`, `SequencePatternMatch`, `StringifyOptions`, `TargetMode`, `WarningEvent` | Supported | [Core keymap API](/docs/keymap/core) |

### Extras and graph

| Package entry point            | Symbols                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | Class     | Canonical documentation              |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ------------------------------------ |
| `@opentui/keymap/extras`       | `BindingCommandMap`, `BindingConfig`, `BindingConfigItem`, `BindingDefaults`, `BindingDefaultsContext`, `BindingLookup`, `BindingValue`, `CommandBindingMap`, `commandBindings`, `CommandBindingsError`, `CommandBindingsOptions`, `CommandBindingsOverrideWarning`, `createBindingLookup`, `CreateBindingLookupOptions`, `formatCommandBindings`, `FormatCommandBindingsOptions`, `formatKeySequence`, `FormatKeySequenceOptions`, `KeyModifierName`, `KeySequenceFormatPart`, `SequenceBindingLike`, `TokenDisplayResolver` | Supported | [Core keymap API](/docs/keymap/core) |
| `@opentui/keymap/extras/graph` | `createGraphExtra`, `getGraphSnapshot`, `GraphBinding`, `GraphCommand`, `GraphInactiveReason`, `GraphLayer`, `GraphSequenceNode`, `GraphSnapshot`, `GraphSnapshotOptions`, `KeymapGraphExtra`                                                                                                                                                                                                                                                                                                                                 | Advanced  | [Core keymap API](/docs/keymap/core) |

### Addons

| Package entry point              | Symbols                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | Class     | Canonical documentation                                                                              |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------- | ---------------------------------------------------------------------------------------------------- |
| `@opentui/keymap/addons`         | `Aliases`, `BackspacePopsPendingSequenceOptions`, `defaultBindingParser`, `defaultEventMatchResolver`, `Enabled`, `EscapeClearsPendingSequenceOptions`, `ExCommand`, `ExCommandPayload`, `LeaderOptions`, `LeaderTrigger`, `NeovimDisambiguationOptions`, `registerAliasesField`, `registerBackspacePopsPendingSequence`, `registerBindingOverrides`, `registerCommaBindings`, `registerDeadBindingWarnings`, `registerDefaultBindingParser`, `registerDefaultEventMatchResolver`, `registerDefaultKeys`, `registerEmacsBindings`, `registerEnabledFields`, `registerEscapeClearsPendingSequence`, `registerExCommands`, `registerLeader`, `registerMetadataFields`, `registerModBindings`, `registerNeovimDisambiguation`, `registerTimedLeader`, `registerUnresolvedCommandWarnings`, `TimedLeaderOptions` | Supported | [Built-in keymap addons](/docs/keymap/addons)                                                        |
| `@opentui/keymap/addons/opentui` | Every export from `@opentui/keymap/addons`, plus `createTextareaBindings`, `EditBufferCommandName`, `EditBufferCommandOptions`, `EditBufferFineGroup`, `registerBaseLayoutFallback`, `registerEditBufferCommands`, `registerManagedTextareaLayer`, `registerTextareaMappingSuspension`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | Supported | [Built-in keymap addons](/docs/keymap/addons) and [Custom keymap addons](/docs/keymap/custom-addons) |

### Testing and hosts

| Package entry point       | Runtime                  | Symbols                                                                                                                                                                                                                                                                                                                                              | Class            | Canonical documentation              |
| ------------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | ------------------------------------ |
| `@opentui/keymap/testing` | JavaScript test runtimes | `captureKeymapDiagnostics`, `createTestHostMetadata`, `createTestKeymap`, `createTestKeymapHost`, `CreateTestKeymapHostOptions`, `CreateTestKeymapOptions`, `DEFAULT_TEST_HOST_METADATA`, `TestDiagnosticCapture`, `TestHostMetadataOptions`, `TestKeymapEvent`, `TestKeymapHarness`, `TestKeymapHost`, `TestKeymapTarget`, `TestKeyModifierOptions` | Supported        | [Core keymap API](/docs/keymap/core) |
| `@opentui/keymap/html`    | Browser DOM              | `createDefaultHtmlKeymap`, `createHtmlKeymap`, `createHtmlKeymapEvent`, `createHtmlKeymapHost`, `htmlEventMatchResolver`, `HtmlKeymapEvent`, `normalizeHtmlKeyName`                                                                                                                                                                                  | Runtime-specific | [Keymap hosts](/docs/keymap/hosts)   |
| `@opentui/keymap/opentui` | Bun and Node.js          | `createDefaultOpenTuiKeymap`, `createOpenTuiKeymap`, `createOpenTuiKeymapHost`                                                                                                                                                                                                                                                                       | Runtime-specific | [Keymap hosts](/docs/keymap/hosts)   |

### Frameworks and runtime modules

| Package entry point               | Symbols                                                                                                                                                                            | Class                      | Canonical documentation                                |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------ |
| `@opentui/keymap/react`           | `KeymapProvider`, `KeymapProviderProps`, `reactiveMatcherFromStore`, `useActiveKeys`, `useBindings`, `UseBindingsLayer`, `UseBindingsTargetRef`, `useKeymap`, `usePendingSequence` | Runtime-specific           | [React keymap integration](/docs/keymap/react)         |
| `@opentui/keymap/solid`           | `KeymapProvider`, `KeymapProviderProps`, `reactiveMatcherFromSignal`, `useBindings`, `UseBindingsLayer`, `UseBindingsTarget`, `useKeymap`, `useKeymapSelector`                     | Runtime-specific           | [Solid keymap integration](/docs/keymap/solid)         |
| `@opentui/keymap/runtime-modules` | `runtimeModules`                                                                                                                                                                   | Runtime-specific, Advanced | [Runtime module loading](/docs/extend/runtime-plugins) |

The `runtimeModules` map contains the root, both extras paths, both addon paths, and both host adapters. It loads the React and Solid paths lazily. It does not contain `/testing` or `/runtime-modules`.

## `@opentui/qrcode`

The package root works in Bun and Node.js. Each framework path re-exports the full root and adds its registration function.

| Package entry point     | Symbols                                                                                                                                                                                                                                                                          | Class            | Canonical documentation                                                                    |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------ |
| `@opentui/qrcode`       | `ByteEncoding`, `createQrSvg`, `EciAssignment`, `EncodeOptions`, `encodeTextBytes`, `ErrorCorrectionLevel`, `Fnc1SecondPositionInfo`, `Gs1Element`, `QRCode`, `QRCodeFitMode`, `QRCodeOptions`, `QRCodeRenderable`, `QrSegment`, `StructuredAppendInfo`, `TerminalRenderOptions` | Supported        | [QR encoder](/docs/reference/qr-encoder) and [QR code component](/docs/components/qr-code) |
| `@opentui/qrcode/react` | Every root export, plus `registerQRCode`                                                                                                                                                                                                                                         | Runtime-specific | [QR code component](/docs/components/qr-code)                                              |
| `@opentui/qrcode/solid` | Every root export, plus `registerQRCode`                                                                                                                                                                                                                                         | Runtime-specific | [QR code component](/docs/components/qr-code)                                              |

## `@opentui/ssh`

The package supports Bun 1.3.14 or later and Node.js 26.4.0 or later. Use Bun 1.4.0 or later on native Windows arm64.
It has one public entry point.

| Symbols                                                                                                                                                                                                                                                                                                                                                                                           | Class            | Canonical documentation    |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -------------------------- |
| `AuthConfig`, `AuthMethods`, `ConfigError`, `createServer`, `DenyError`, `Handoff`, `Identity`, `IdentityFor`, `ListenInfo`, `LogEvent`, `logging`, `LoggingOptions`, `Middleware`, `MiddlewareFunction`, `MiddlewareSession`, `Next`, `OutputPressureError`, `PublicKey`, `PublicKeyPolicy`, `RemoteAddress`, `Server`, `ServerBuilder`, `ServerConfig`, `Session`, `SessionHandler`, `SshError` | Runtime-specific | [SSH](/docs/reference/ssh) |

## `@opentui/three`

Both entry points are Bun-only.

| Package entry point              | Symbols                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | Class                      | Canonical documentation                                |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------ |
| `@opentui/three`                 | `AnimationDefinition`, `AnimationStateConfig`, `CLICanvas`, `DEFAULT_EXPLOSION_PARAMETERS`, `DEFAULT_PHYSICS_EXPLOSION_PARAMETERS`, `ExplodingSpriteEffect`, `ExplosionCreationData`, `ExplosionEffectParameters`, `ExplosionHandle`, `ExplosionManager`, `InstanceManager`, `InstanceManagerOptions`, `MeshPool`, `MeshPoolOptions`, `ParticleEffectParameters`, `PhysicsExplodingSpriteEffect`, `PhysicsExplosionCreationData`, `PhysicsExplosionEffectParameters`, `PhysicsExplosionHandle`, `PhysicsExplosionManager`, `PhysicsSpriteRecreationData`, `PlanckPhysicsWorld`, `PlanckRigidBody`, `RapierPhysicsWorld`, `RapierRigidBody`, `ResolvedAnimationState`, `ResourceConfig`, `SheetProperties`, `SheetSprite`, `SpriteAnimator`, `SpriteDefinition`, `SpriteParticleGenerator`, `SpriteRecreationData`, `SpriteResource`, `SpriteResourceManager`, `SpriteUtils`, `SuperSampleAlgorithm`, `SuperSampleType`, `TextureUtils`, `THREE`, `ThreeCliRenderer`, `ThreeCliRendererOptions`, `ThreeRenderable`, `ThreeRenderableOptions`, `TiledSprite` | Runtime-specific, Advanced | [Three.js WebGPU](/docs/reference/three)               |
| `@opentui/three/runtime-modules` | `runtimeModules`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | Runtime-specific, Advanced | [Runtime module loading](/docs/extend/runtime-plugins) |

The `THREE` namespace exposes the installed Three.js module. The `runtimeModules` map contains only the `@opentui/three` root.

## Native distribution packages

The build generates eight platform packages:

- `@opentui/core-darwin-x64`
- `@opentui/core-darwin-arm64`
- `@opentui/core-linux-x64`
- `@opentui/core-linux-arm64`
- `@opentui/core-linux-x64-musl`
- `@opentui/core-linux-arm64-musl`
- `@opentui/core-win32-x64`
- `@opentui/core-win32-arm64`

Each package has one default export that resolves the native library path. These packages are internal distribution surfaces, not application APIs. Let `@opentui/core` select the correct package. See [Runtime and platform support](/docs/getting-started/runtime-support).

## `opentui.h`

The npm API and the native application binary interface (ABI) are separate surfaces. `opentui.h` declares the experimental C ABI. See [C](/docs/native/c). The [C ABI reference](/docs/api/c) lists every declaration with the release that added it.

OpenTUI's TypeScript packages call the native library through internal bindings that can change with them. Use the TypeScript exports indexed above for application code.

These rows name the declarations that the native guides document. The comments in `opentui.h` document every declaration.

| Area                   | Symbols                                                                                                                                                                                                                                                                                                                                                                     | Canonical documentation                                                                                         |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| ABI version            | `OT_CONTEXT_ABI_VERSION`                                                                                                                                                                                                                                                                                                                                                    | [Build the library](/docs/native/c#build-the-library)                                                           |
| Contexts               | `ot_context`, `ot_context_create`, `ot_context_destroy`, `ot_context_options`                                                                                                                                                                                                                                                                                               | [Group resources in a Context](/docs/native/resources#group-resources-in-a-context)                             |
| Handles                | `ot_handle`                                                                                                                                                                                                                                                                                                                                                                 | [A handle names an object](/docs/native/resources#a-handle-names-an-object)                                     |
| Results                | `ot_status`, `OT_OK`, `OT_INVALID_ARGUMENT`, `OT_UNSUPPORTED_VERSION`, `OT_WRONG_CONTEXT`, `OT_WRONG_SESSION`, `OT_WRONG_KIND`, `OT_STALE_HANDLE`, `OT_WRONG_THREAD`, `OT_CONTEXT_BUSY`, `OT_FRAME_BUSY`, `OT_OUTPUT_BACKPRESSURE`, `OT_STALE_FRAME`, `OT_STALE_OUTPUT`, `OT_STALE_LEASE`                                                                                   | [Initialize records and check results](/docs/native/c#initialize-records-and-check-results)                     |
| Sessions               | `ot_session_create`, `ot_session_options`, `ot_session_attach_renderer`, `ot_session_renderer_options`, `ot_session_setup_terminal`, `OT_INVALID_PHASE`                                                                                                                                                                                                                     | [Add a Session for output](/docs/native/resources#add-a-session-for-output)                                     |
| Shared resources       | `ot_buffer_create`, `ot_text_buffer_create`, `ot_text_buffer_view_create`, `ot_edit_buffer_create`, `ot_editor_view_create`, `ot_syntax_style_create`, `ot_scene_set_text_view`, `OT_SCENE_TEXT_VIEW`                                                                                                                                                                       | [Create shared resources in one Context](/docs/native/resources#create-shared-resources-in-one-context)         |
| Images                 | `ot_image_decode`, `ot_image_inspect`, `ot_image_create_pixels`, `ot_image_take_pixels`, `ot_image_pixels_release`, `ot_image_clone`, `ot_image_retain`                                                                                                                                                                                                                     | [Create images in a Context](/docs/native/resources#create-images-in-a-context)                                 |
| Buffer leases          | `ot_buffer_acquire_lease`, `ot_buffer_lease_validate`, `ot_buffer_lease_get_real_char_size`, `ot_buffer_lease_write_resolved_chars`, `ot_buffer_lease_release`                                                                                                                                                                                                              | [Borrow cells for one synchronous operation](/docs/native/resources#borrow-cells-for-one-synchronous-operation) |
| Owned buffers          | `ot_scene_set_surface`, `ot_scene_frame_draw_buffer`, `ot_session_draw_buffer`, `OT_SCENE_CUSTOM`                                                                                                                                                                                                                                                                           | [Compose an owned buffer](/docs/native/resources#compose-an-owned-buffer)                                       |
| Text and geometry      | `ot_scene_get_layout`, `ot_scene_set_styled_text`, `ot_styled_text_chunk`, `ot_scene_text_info`, `OT_LAYOUT_PUBLIC`, `OT_LAYOUT_YOGA`, `OT_LAYOUT_PAINT`                                                                                                                                                                                                                    | [Read text and geometry](/docs/native/resources#read-text-and-geometry)                                         |
| Teardown               | `ot_scene_destroy_node`, `ot_session_cancel`, `ot_session_close`, `ot_session_destroy`, `OT_CLIPBOARD_START_SHUTTING_DOWN`                                                                                                                                                                                                                                                  | [Finish work before destroying owners](/docs/native/resources#finish-work-before-destroying-owners)             |
| Scene nodes            | `ot_scene_create_node`, `ot_scene_move_node`, `ot_scene_set_paint`, `ot_scene_paint_options`, `ot_scene_set_text`, `OT_SCENE_ROOT`, `OT_SCENE_BOX`, `OT_SCENE_TEXT`                                                                                                                                                                                                         | [Add the box and label](/docs/native/c#add-the-box-and-label)                                                   |
| Host drawing           | `ot_buffer_draw_text_record`, `ot_scene_record_draw`, `ot_scene_record_slot`, `OT_BUFFER_DRAW_TEXT`, `OT_SCENE_RECORD_DRAW`, `OT_SCENE_RECORD_SLOT`, `OT_SCENE_RECORD_PHASE_AFTER`                                                                                                                                                                                          | [Add host drawing](/docs/native/c#add-host-drawing)                                                             |
| Paint and deliver      | `ot_scene_paint`, `ot_scene_frame_request`, `ot_scene_frame_commit`, `ot_session_complete_output`, `OT_UNSUPPORTED_RESOURCE`                                                                                                                                                                                                                                                | [Paint, submit, and deliver](/docs/native/frames#paint-submit-and-deliver)                                      |
| Frame requests         | `ot_scene_frame_step_with_geometry`, `ot_scene_frame_geometry`, `ot_scene_frame_options`, `ot_scene_set_hooks`, `ot_scene_hooks`, `OT_SCENE_FRAME_DONE`, `OT_SCENE_FRAME_UPDATE`, `OT_SCENE_FRAME_RESIZE`, `OT_SCENE_FRAME_LAYOUT_CHANGED`, `OT_SCENE_FRAME_RECORD`, `OT_SCENE_FRAME_YIELD`, `OT_SCENE_HOOK_UPDATE`, `OT_SCENE_HOOK_RESIZE`, `OT_SCENE_HOOK_LAYOUT_CHANGED` | [Read and acknowledge requests](/docs/native/frames#read-and-acknowledge-requests)                              |
| Paint hooks            | `ot_scene_frame_get_paint_slots`, `ot_scene_paint_slot`, `ot_scene_record_header`, `ot_scene_hit_test`, `ot_scene_set_image`, `OT_SCENE_HOOK_RENDER_BEFORE`, `OT_SCENE_HOOK_RENDER_SELF`, `OT_SCENE_HOOK_RENDER_AFTER`, `OT_SCENE_RECORD_BYTES_MAX`, `OT_BUFFER_DRAW_CLEAR`                                                                                                 | [Record paint hooks](/docs/native/frames#record-paint-hooks)                                                    |
| Measurement            | `ot_scene_set_measure`, `ot_scene_measure_callback`                                                                                                                                                                                                                                                                                                                         | [Measure custom content](/docs/native/frames#measure-custom-content)                                            |
| Property updates       | `ot_scene_flush`, `OT_SCENE_PROPERTY_STYLE`, `OT_SCENE_MUTATIONS_MAX`, `OT_SCENE_PROPERTY_BYTES_MAX`                                                                                                                                                                                                                                                                        | [Submit property updates](/docs/native/frames#submit-property-updates)                                          |
| Changes during a frame | `ot_scene_frame_cancel`, `ot_scene_set_viewport`, `OT_FRAME_REQUEST_LIMIT`, `OT_LAYOUT_LIMIT`                                                                                                                                                                                                                                                                               | [Change the scene during a frame](/docs/native/frames#change-the-scene-during-a-frame)                          |
| Framebuffer access     | `ot_scene_frame_acquire_buffer_lease`, `ot_session_acquire_buffer_lease`                                                                                                                                                                                                                                                                                                    | [Access the framebuffer](/docs/native/frames#access-the-framebuffer)                                            |
| Rendering and output   | `ot_session_render`, `ot_session_render_split`, `ot_session_read_output`, `ot_session_drain_output`, `ot_session_drain_stdout`, `ot_output_ticket`, `OT_RENDER_PRESENTED`, `OT_RENDER_PENDING`, `OT_RENDER_SKIPPED`, `OT_RENDER_FAILED`                                                                                                                                     | [Commit the draft, then complete output](/docs/native/frames#commit-the-draft-then-complete-output)             |
| Output storage         | `ot_session_get_write_limit`, `OT_SESSION_CONTROL_PACKET_BYTES`                                                                                                                                                                                                                                                                                                             | [Reserve output storage](/docs/native/frames#reserve-output-storage)                                            |
| Terminal work          | `ot_session_pump`, `ot_session_control`, `OT_PUMP_IDLE`, `OT_PUMP_AGAIN`, `OT_PUMP_WAIT_UNTIL`, `OT_PUMP_OUTPUT_PENDING`, `OT_PUMP_CLOSED`, `OT_CONTROL_CAPABILITY_RESPONSE`                                                                                                                                                                                                | [Pump terminal work and close](/docs/native/frames#pump-terminal-work-and-close)                                |
| Host output            | `OT_OUTPUT_BUSY`, `OT_OUTPUT_FAILED`                                                                                                                                                                                                                                                                                                                                        | [Output without a host copy](/docs/native/host-io-time#output-without-a-host-copy)                              |
| Deadlines              | `ot_session_pump_exit`, `ot_session_poll_kitty_image_transport`                                                                                                                                                                                                                                                                                                             | [One Session deadline clock](/docs/native/host-io-time#one-session-deadline-clock)                              |
| Core integration       | `ot_context_set_edit_event_callback`, `ot_edit_event_callback`, `OT_BUFFER_TEXT_BYTES_MAX`                                                                                                                                                                                                                                                                                  | [How Core uses native](/docs/native/core)                                                                       |

## Changes since 0.5.14

Layout, hit testing, and painting now run in a native scene. Native resources belong to an owner Context.
This section lists each 0.5.14 API and behavior that was removed or changed, and what to use instead.
Each item names the changed API or behavior first. New APIs are in the tables above.

### Renderer

- `addToHitGrid()`, `pushHitGridScissorRect()`, `popHitGridScissorRect()`, and `clearHitGridScissorRects()`: removed from the renderer and `RenderContext`. Native code adds a hit area for each visible node and clips it to `overflow` ancestors. Use `renderer.hitTest(x, y)` to query the hit grid.
- `dumpBuffers()` and `dumpOutputBuffer()`: removed. Use test frame capture, `renderer.currentRenderBuffer.withBuffers()`, or a custom `stdout`. `dumpHitGrid()` remains.
- `renderer.useThread` and the `useThread` option of `createCliRenderer()` and `createTestRenderer()`: removed. Core writes output on the JavaScript thread. Remove the option.
- `renderer.rendererPtr`: removed, with no replacement.
- `OTUI_NO_NATIVE_RENDER` environment variable: removed. Use `createTestRenderer()`.
- `suspend()` and `resume()`: now return a `Promise<void>`. Await them. They throw when the terminal is not set up (also after `destroy()`) or a transition is pending. `resume()` also throws when the renderer is not suspended.
- `useMouse`, `enableKittyKeyboard()`, and `disableKittyKeyboard()`: now throw during terminal setup, suspend, or resume, and while the renderer is suspended. `screenMode` now throws during setup, suspend, or resume. Change them after the transition completes.
- `enableKittyKeyboard(flags)`: now throws `RangeError` when `flags` is not an integer from 0 to 31.
- Output stream `error` event: now closes the renderer and rejects `renderer.closed`. Handle the rejection of `closed`. In 0.5.14, the renderer handled the event only while Kitty file transport was active. It cancelled file transport and kept running. A failed Kitty temporary-file write still falls back to inline transport.
- Renderer methods after `destroy()`: `setTerminalTitle()`, `resetTerminalBgColor()`, `copyToClipboardOSC52()`, `clearClipboardOSC52()`, `isOsc52Supported()`, `setCursorPosition()`, `setCursorStyle()`, `setCursorColor()`, `setMousePointer()`, `getCursorState()`, `getStats()`, `getNativeStats()`, `configureDebugOverlay()`, `toggleDebugOverlay()`, `dumpHitGrid()`, `hitTest()`, and `console.show()` (also through `console.toggle()`) throw. `getPalette()` rejects. `requestRender()`, `resize()`, `triggerNotification()`, and `getLinkAt()` do nothing. Check `renderer.isDestroyed` first.
- `getPalette()`: now rejects before the terminal is set up, for example on a `createTestRenderer()` renderer. It also rejects while a setup, suspend, or resume is pending. Its `size` must be an integer from 1 to 256.
- Renderable constructors and `engine.attach()`: now throw for a destroyed renderer. Create renderables before you destroy the renderer.
- `destroy()`: restores the terminal and releases the streams asynchronously. Await `renderer.closed` before you reuse the streams.
- Scrollback snapshots and commits: one snapshot or commit larger than the output capacity divided by 24 bytes (about 349,000 cells by default) throws. A `rowColumns` value that is not an integer from 0 to the snapshot width throws `RangeError`. In 0.5.14, Core truncated and clamped `rowColumns`.

### Renderables and layout

- `RootRenderable.calculateLayout()`: removed. Layout runs in the native frame step. Read `getLayout()` after `renderOnce()` or `await renderer.idle()`.
- `Renderable.getLayoutNode()` and the protected `yogaNode`: removed. Use layout properties, `setMeasureProvider()`, `invalidateIntrinsicSize()`, and `getLayout()`. Replace a style read such as `getLayoutNode().getMaxWidth()` with the renderable method of the same name, `getMaxWidth()`. Replace `markDirty()` with `invalidateIntrinsicSize()`, and the computed position and size getters with `getLayout()`. Computed padding, margin, and border have no replacement. Renderables cannot use `Display.Contents`. The standalone `Yoga` API remains.
- `render()`, `updateLayout()`, `updateFromLayout()`, `canReuseRenderCommandList()`, and the `RenderCommand` type: removed. Override `renderSelf(buffer, deltaTime)`, or use the `renderBefore` and `renderAfter` paint hooks. In a subclass of `TextBufferRenderable` or `EditBufferRenderable`, the override has one parameter: `renderSelf(buffer)`.
- Protected `_x`, `_y`, `_widthValue`, `_heightValue`, `_childrenInZIndexOrder`, `_getVisibleChildren()`, `_hasVisibleChildFilter()`, and `getScissorRect()`: removed. Use `x`, `y`, `width`, `height`, `screenX`, and `screenY`. For child culling, use the `viewportCulling` option of `ScrollBox`.
- Protected `renderCursor()` of edit buffer renderables: removed. Use the cursor options.
- Protected `updateViewportOffset()`: `TextRenderable` no longer calls it when it scrolls or resizes. Native code applies the viewport.
- Protected color fields of `ASCIIFontRenderable`: now hold `RGBA` values.
- Custom `RenderContext`: must now implement `nativeScene`, `requestAnimationFrame()`, and `cancelAnimationFrame()`.
- Paint hooks (`renderBefore`, `renderSelf`, and `renderAfter`): now receive a recording buffer. `buffers`, `withBuffers()`, `getRealCharBytes()`, and `getSpanLines()` throw in a hook. Draw into a buffer that you own and compose it, or use a post-process function. A `buffered: true` custom renderable is different: its hooks receive its own frame buffer.
- Changes made in a paint hook: a layout, position, visibility, child, or `zIndex` change takes effect in the next frame. Color and text changes paint in the same frame.
- `on("resized")` and `on("layout-changed")`: now throw on a renderable that is not the root. These listeners never ran in 0.5.14. Use `onSizeChange`.
- Geometry after `destroy()`: `x`, `y`, `screenX`, `screenY`, `width`, and `height` of a renderable that is not `selectable` return 0 or the styled size, not the last layout. Read them before you destroy the renderable.
- `RenderContext.getLifecyclePasses()`: now contains only renderables whose lifecycle pass is active.
- Box `customBorderChars`: wide, combining, or control characters now throw.

### Buffers

- `OptimizedBuffer.create(width, height, widthMethod, options)`: now requires `options.owner`. Pass `renderer.nativeScene` or a `ResourceContext`.
- `buffer.buffers`: works only in a post-process function. Use `buffer.withBuffers(callback)` elsewhere.
- Drawing into `renderer.nextRenderBuffer`: works only in a post-process function. `renderer.currentRenderBuffer` does not accept drawing. In 0.5.14, frame callbacks could draw into `nextRenderBuffer`, for example with `ThreeCliRenderer.drawScene()`. Draw into a `FrameBufferRenderable` instead.
- `encodeUnicode(text)`: now returns `{ data }` and never `null`. Its `char` values are opaque tokens for `drawChar()` on a buffer with the same owner. A tab encodes as two one-cell spaces.
- `buffer.ptr` and `getNativeId()`: removed. Keep the buffer object. `buffer.id` is only a label.
- Drawing methods: now throw `RangeError` for fractional, `NaN`, or infinite coordinates and sizes. Round them first.
- `drawText()` and box titles: now throw `RangeError` for text larger than 64 KiB of UTF-8.
- `pushOpacity()` and color-matrix strength: a non-finite value now throws.
- Composing a buffer into itself now throws. `setRespectAlpha()` on a renderer buffer throws.
- `setCell()`, `setCellWithAlphaBlending()`, `drawChar()`, and `drawText()`: now reject attribute bits 8 to 31. Pass only text attributes. Use the styled-text `link()` helper for hyperlinks.

### Text and editing

- `SyntaxStyle.create()`, `fromStyles(styles)`, and `fromTheme(theme)`: now `create(owner)`, `fromStyles(styles, owner)`, and `fromTheme(theme, owner)`. Pass `renderer.nativeScene`, or a `ResourceContext` for standalone use.
- `TextBuffer.create(widthMethod)` and `EditBuffer.create(widthMethod)`: now `create(widthMethod, owner)`.
- `.ptr` on `TextBuffer`, `TextBufferView`, `EditBuffer`, `EditorView`, and `SyntaxStyle`: removed. Their constructors take Context handles. Use the `create()` factories.
- `EditBuffer.setText()`, `insertText()`, and `replaceText()`, the same Textarea and Input methods, Textarea `initialValue`, `Input.value`, and the `placeholder` of both: now show control characters other than tab, carriage return, and line feed as notation, such as `^L` or `<85>`. In 0.5.14, the cursor could not move past or delete them, and the terminal received them.
- `TextBuffer.loadFile()`: now rejects invalid UTF-8.
- Text buffer and text view offsets: must be integers. Other values throw `RangeError`.
- Highlight `priority` above 255 or `hlRef` above 65535: now throws. In 0.5.14, the value wrapped.
- Color getters of text, text table, ASCII font, Markdown, Diff, line number, and QR code renderables, and the `SyntaxStyle` style getters: now return copies. Change a color through its setter. `ASCIIFont.color` returns an `RGBA`.

### Resources and native bindings

- Minimum Bun version: now 1.3.14 (was 1.3.0), the tested minimum. Bun 1.3.0 to 1.3.3 convert some FFI pointer arguments to wrong addresses. Bun 1.3.4 to 1.3.6 can crash after a Worker exits. Bun 1.3.7 intermittently hangs while it loads modules.
- `Clipboard` class: removed. Use `renderer.copyToClipboardOSC52()` and `clearClipboardOSC52()`, or `createClipboard()` with `createHostClipboard()` and `createRendererClipboardAdapter(renderer)`.
- Native resources: each resource now belongs to one owner Context. Resources from different owners cannot be used together. Drawing a `NativeImage` into a renderer copies it into the renderer's Context.
- Images that `ImageRenderable` loads: belong to the renderer's Context and throw after `renderer.destroy()`. To keep an image, load it yourself, for example with `NativeImage.load()`, and pass it as `source`.
- `getAllocatorStats()`: does not count Context memory. `getArenaAllocatedBytes()` always returns 0. Use `process.memoryUsage().rss` for total memory.
- Handle types `RendererHandle`, `OptimizedBufferHandle`, `TextBufferHandle`, `TextBufferViewHandle`, `EditBufferHandle`, `EditorViewHandle`, `SyntaxStyleHandle`, `EventSinkHandle`, `NativeRenderableHandle`, `EmbeddedTerminalHandle`, `NativeMeasureTargetHandle`, `NativeMeasureTargetKind`, `NativeRenderOperationResult`, and `NativeRendererCreateOptions`: removed. Use the `Context*Handle` types, `SessionHandle`, and `SceneNodeHandle`. `ImageHandle` is now an alias of `ContextImageHandle`.
- Pointer-based `RenderLib` methods, such as `createRenderer`, `createOptimizedBuffer`, `bufferDrawText`, `setUseThread`, and `nativeRenderableSetMeasureTarget`: removed. Use the public classes. Standalone `Yoga` nodes measure only with JavaScript measure functions.
- Standalone `Yoga`: `Config.free()` now throws while nodes from that config remain. Invalid arguments, such as an out-of-range child index or enum value, throw `YogaError`. Measure and dirtied functions must return synchronously and must not change Yoga nodes. See [Yoga API](/docs/reference/yoga#measure-and-dirtied-callbacks).

### SSH

- `@opentui/ssh` `session.write()`: can now throw `OutputPressureError` or `RangeError`. `cols` and `rows` hold the requested size until the renderer attaches. `onClose` runs at logical close, before the renderer finishes closing.

### Native libraries

- Legacy native exports, such as `createRenderer` and `bufferDrawText`: removed. Use the `ot_*` C ABI in `opentui.h`. See [C](/docs/native/c).
- Zig `GraphemePool.alloc()`, `GraphemePool.allocUnowned()`, and `LinkPool.alloc()`: replaced by `acquire()`. It copies the bytes and returns an ID that holds one reference. Call `decref()` to release it. `alloc()` returned an ID with no references, and `allocUnowned()` did not copy.
- Zig `GraphemePool.freeUnreferenced()` and the global pools (`initGlobalPool()`, `initGlobalPoolWithOptions()`, `deinitGlobalPool()`, `initGlobalLinkPool()`, and `deinitGlobalLinkPool()`): removed. Use explicit pools or `opentui.Context`. See [Zig](/docs/native/zig).
- Zig `UnifiedTextBuffer.setStyledText()` and `StyledChunk`: removed. Use `replaceOwnedStyledText()` with `OwnedStyledChunk`. `clear()` and `reset()` can now return errors.
- Zig `CliRenderer.dumpBuffers()` and `dumpOutputBuffer()`: removed.

---

### Packages React Docs EXTEND (`packages/react/docs/EXTEND.md`)

# OpenTUI React Component Extension

The `extend` function adds custom renderable classes to the component catalogue of the OpenTUI React reconciler. It works like `extend` in `@react-three/fiber`, which adds Three.js objects.

## Basic Usage

### Extending Components

```tsx
import { BoxRenderable, OptimizedBuffer, RGBA, type BoxOptions, type RenderContext } from "@opentui/core"
import { extend } from "@opentui/react"

class ConsoleButton extends BoxRenderable {
  public label: string = "Button"

  constructor(ctx: RenderContext, options: BoxOptions & { label: string }) {
    super(ctx, options)
    // Custom initialization

    this.height = 3
    this.width = 24
  }

  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))
  }
}

declare module "@opentui/react" {
  interface OpenTUIComponents {
    consoleButton: typeof ConsoleButton
  }
}

// Extend components
extend({
  consoleButton: ConsoleButton,
})

// Now you can use them in JSX
function App() {
  return <consoleButton label="Click me!" />
}
```

React calls the constructor with `{ id, ...props }`. After construction, React assigns each prop to the instance again. In this example, React sets `label` after the class field sets it to `"Button"`.

`renderSelf()` is a [paint hook](https://opentui.com/docs/extend/custom-renderables#use-paint-hooks). It uses screen cell coordinates, so the example places the label relative to `this.x` and `this.y`.

## TypeScript Support

For full TypeScript support, declare your extended components with module augmentation:

```tsx
// In your component file or declaration file
declare module "@opentui/react" {
  interface OpenTUIComponents {
    consoleButton: typeof ConsoleButton
  }
}

// Then extend and use with full type safety
extend({
  consoleButton: ConsoleButton,
})

// TypeScript will now know about these components
<consoleButton label="Typed!" />
```

## API Reference

### `extend(components)`

Adds renderable components to the component catalogue.

**Parameters:**

- `components`: Object mapping component names to renderable constructors

**Returns:**

- `void`

### `getComponentCatalogue()`

Returns the current component catalogue. The reconciler uses it to find the class for each JSX element.

## Best Practices

1. **Declare types with module augmentation**: TypeScript then completes and checks the props of each custom element.

2. **Call `requestRender()`**: Call `requestRender()` in each property setter that changes what the component draws.

3. **Extend from appropriate base classes**: Use `BoxRenderable` for containers, `TextRenderable` for text, and so on.

## Limitations

- Extended components must extend from OpenTUI's core renderable classes
- `extend()` replaces an existing entry with the same name, including a built-in component. Use unique names.
- TypeScript support requires manual module augmentation declarations

---

### Packages Core Src Benchmark Docs Ffi Fast Path (`packages/core/src/benchmark/docs/ffi-fast-path.md`)

# FFI Fast Path Benchmarks

## Purpose

This suite measures OpenTUI FFI wrapper overhead in Bun and Node without changing native behavior. It exists because an optimization can help one runtime and regress the other.

## Constraints

- The suite measures Bun and Node together. An optimization for one runtime must not materially regress the other.
- Public result objects stay fresh, and returned byte ranges stay independently owned.
- Experimental native signature or layout changes are not retained.

## Rejected Alternatives

- Packed/descriptor ABI rewrites that regressed a runtime, regressed another workload, or did not produce reliable reports.
- One-pointer seven-argument specialization because it was slower.
- Shared transfer-based trimming because it regressed Bun.

## Method

Scenarios use production wrappers and live native objects. Setup, calibration, verification, and teardown are outside the retained sample. Output probes reject incorrect work.

The retired RendererHandle split-transition and split-snapshot calls are no longer benchmark scenarios.
The remaining scenarios cover standalone buffers, text, editors, images, audio, and span decoding.
Use the render workload preflights and packed-distribution fixtures for retained Context/Session frame and split-output checks.
Historical comparisons must select matching retained scenarios from recorded reports or frozen revisions.

`ffi-fast-path-paired-benchmark.ts` is the preferred comparison. It balances revision order, runs retained batches sequentially, records provenance and diagnostics, and reports paired nominal and multiplicity-adjusted bootstrap intervals. Negative deltas are faster. Safety requires at least 10 pairs and an adjusted upper bound at or below a 3% regression.

Calibration failures retry a complete pair. The report includes all retained timing and pair-gap drift without censoring. Lifecycle failures abort the run.

`ffi-fast-path-benchmark.ts` creates independent reports, `ffi-fast-path-compare.ts` applies the stricter ABI-admission gate to them, and `ffi-fast-path-stress.ts` diagnoses x64 Node process lifecycle failures. The repository intentionally stores no results for this suite. Regenerate them for the revisions and environment that you evaluate.

## Run

From `packages/core`, set `NODE26_PATH` to Node 26.4 or later:

```sh
export NODE26_PATH=/absolute/path/to/node
bun run bench:ffi-fast-path --list-targeted-scenarios
# Run in the prepared baseline worktree.
bun run bench:ffi-fast-path --suite=default --runs=9 --json=/tmp/base.json
# Run in the candidate worktree.
bun run bench:ffi-fast-path --suite=default --runs=9 --json=/tmp/candidate.json
bun run bench:ffi-fast-path-paired --baseline-root=/absolute/base --candidate-root=/absolute/candidate --runs=40
bun run bench:ffi-fast-path-compare /tmp/base.json /tmp/candidate.json
```

Default runs omit the separately listed reusable-storage scenarios. Pass their comma-separated names with `--scenario=<names>` to either runner.

Run paired comparisons from the candidate worktree. Roots must be absolute and use matching scenario/calibration sources. Their native libraries must match unless you pass `--allow-native-drift`. Pair counts must be even. Worktrees must be clean unless you pass `--allow-dirty`.

If the baseline predates this suite, copy `ffi-fast-path-scenarios.ts` and `ffi-fast-path-calibration.ts` from the candidate into the same baseline paths, then pass `--allow-dirty`. The report records the copied files.

---

### Packages Web Src Content Docs Application Apis Animation (`packages/web/src/content/docs/application-apis/animation.mdx`)

---
title: Animation and Timeline
description: Animate numeric state with Timeline and connect updates to the renderer and framework hooks
skill:
  entry: true
  intents: [animation, timeline, easing, use-timeline]
---

# Animation and Timeline

OpenTUI animates mutable numeric properties with `Timeline`. Each renderer can own an independent timeline engine.

Use a timeline for numeric renderable properties or application state. The API mutates each target directly.

## Animate a renderable

Pass the renderer when you create a registered timeline.

```typescript
import { BoxRenderable, createCliRenderer, createTimeline, getTimelineEngine } from "@opentui/core"

const renderer = await createCliRenderer()
const engine = getTimelineEngine(renderer)
const root = renderer.root

const box = new BoxRenderable(renderer, {
  id: "moving-box",
  position: "absolute",
  left: 0,
  top: 1,
  width: 8,
  height: 3,
  backgroundColor: "blue",
})
root.add(box)

let finish!: () => void
const finished = new Promise<void>((resolve) => {
  finish = resolve
})

const timeline = createTimeline(
  {
    duration: 600,
    autoplay: false,
    onComplete: finish,
  },
  renderer,
)

timeline.add(box, {
  left: 30,
  duration: 600,
  ease: "outQuad",
})

timeline.play()

try {
  await finished
} finally {
  timeline.pause()
  engine.unregister(timeline)
  renderer.destroy()
  await renderer.closed
}
```

The engine calls `renderer.requestLive()` and `renderer.dropLive()` as registered timelines start and stop. Do not call `requestLive()` for a registered timeline.

See [Renderer live rendering](/docs/core-concepts/renderer#live-rendering) for the shared live counter.

## Imports

Core exports the class, factory, renderer-owned engine lookup, default engine, and public types from the package root:

```typescript
import {
  Timeline,
  createTimeline,
  getTimelineEngine,
  engine,
  type AnimationOptions,
  type EasingFunctions,
  type JSAnimation,
  type TimelineOptions,
} from "@opentui/core"
```

Framework hooks use their framework package roots:

```tsx
import { useTimeline } from "@opentui/react"
```

```tsx
import { useTimeline } from "@opentui/solid"
```

## Choose a constructor

Construction and registration are separate behaviors.

| API                                    | Initial state                               | Registration                                            |
| -------------------------------------- | ------------------------------------------- | ------------------------------------------------------- |
| `new Timeline(options?)`               | Always paused, including `autoplay: true`   | Not registered                                          |
| `createTimeline(options?, renderer?)`  | Plays unless `autoplay` is `false`          | Uses the renderer's engine, or `engine` when omitted    |
| React or Solid `useTimeline(options?)` | Plays on mount unless `autoplay` is `false` | Registers with the component's renderer engine on mount |

`new Timeline()` stores the `autoplay` option but does not act on it. When you use the class directly, call `play()` and `getTimelineEngine(renderer).register(timeline)`.

`createTimeline()` calls `play()` before it registers the timeline. Registration then updates the renderer's live state.

The framework hooks create the timeline during framework setup. Their mount handlers play and register it.

## Timeline options

| `TimelineOptions` field | Default | Behavior                                                     |
| ----------------------- | ------- | ------------------------------------------------------------ |
| `duration`              | `1000`  | Timeline cutoff in milliseconds                              |
| `loop`                  | `false` | Restarts the timeline at its cutoff only when exactly `true` |
| `autoplay`              | `true`  | Used by the factory and framework mount handlers             |
| `onComplete`            | None    | Runs when a non-looping timeline reaches its cutoff          |
| `onPause`               | None    | Runs on every `pause()` call                                 |

`duration: 0` falls back to `1000` because the constructor uses a truthy fallback. Use a positive finite duration.

The timeline duration does not derive from its items. A shorter timeline cuts off a longer animation.

A longer timeline stays active after all its items finish. Its `onComplete` runs only at the timeline cutoff.

## Add numeric animation

`add(target, properties, startTime?)` accepts one target or an array of targets. It returns the same timeline.

Additional numeric keys in `properties` are animation end values. The timeline ignores non-numeric end values.

When the item first becomes active, OpenTUI reads each matching top-level target property. It captures only existing numeric values.

The target properties must remain writable numbers. A missing or non-numeric initial property can produce `NaN`, and a read-only property can throw.

Initial values stay captured across timeline loops and `restart()`. The timeline does not recapture them on each run.

The timeline does not support nested property paths, arrays of component values, or object traversal. Animate a top-level numeric property instead.

### `AnimationOptions`

| Field              | Type                               | Behavior                                                            |
| ------------------ | ---------------------------------- | ------------------------------------------------------------------- |
| `duration`         | `number`                           | Item duration in milliseconds. Required by the TypeScript interface |
| `ease`             | `EasingFunctions`                  | Named easing function. Default is `linear`                          |
| `onUpdate`         | `(animation: JSAnimation) => void` | Runs for each active update                                         |
| `onComplete`       | `() => void`                       | Runs after the final item cycle                                     |
| `onStart`          | `() => void`                       | Runs when the item first becomes active                             |
| `onLoop`           | `() => void`                       | Runs when an intermediate item cycle changes                        |
| `loop`             | `boolean \| number`                | Infinite cycles for `true`, or a numeric cycle count                |
| `loopDelay`        | `number`                           | Delay after each item cycle. Default is `0`                         |
| `alternate`        | `boolean`                          | Reverses every odd item cycle. Default is `false`                   |
| `once`             | `boolean`                          | Removes the item after completion. Default is `false`               |
| Other numeric keys | `number`                           | End values for target properties                                    |

Untyped code that omits item `duration` currently falls back to `1000`. TypeScript callers must supply it.

Use a positive integer for numeric `loop`. `false`, `undefined`, and `1` all run one cycle.

### Update callback data

`onUpdate` receives a `JSAnimation` object:

| Field         | Meaning                                                       |
| ------------- | ------------------------------------------------------------- |
| `targets`     | The normalized target array after property mutation           |
| `deltaTime`   | The update delta supplied for this evaluation                 |
| `progress`    | Eased forward progress before `alternate` reverses assignment |
| `currentTime` | Current time of this timeline                                 |

Back and elastic easing can make `progress` less than `0` or greater than `1`. OpenTUI clamps the easing input itself to `0..1`.

Callbacks run synchronously inside `update()`. A thrown error propagates to the caller of `update()`.

In an engine update, the error stops that update. Later items and timelines do not update in that frame. The renderer catches the error from the engine's frame callback, logs it, and continues the frame.

## Easing names

`EasingFunctions` accepts exactly these names:

- `linear`
- `inQuad`
- `outQuad`
- `inOutQuad`
- `inExpo`
- `outExpo`
- `inOutSine`
- `outBounce`
- `outElastic`
- `inBounce`
- `inCirc`
- `outCirc`
- `inOutCirc`
- `inBack`
- `outBack`
- `inOutBack`

The API does not accept a custom easing function through `ease`.

## Schedule items

Use numeric start times in milliseconds.

```typescript
timeline.add(firstTarget, { x: 20, duration: 300 }, 0)
timeline.call(() => startSecondPhase(), 300)
timeline.add(secondTarget, { opacity: 1, duration: 200 }, 300)
```

`add()` and `call()` accept `number | string` at runtime. Every string currently resolves to time `0`.

Strings do not name labels or relative positions. Use finite non-negative numbers for predictable scheduling.

`call(callback, startTime?)` runs the callback once when timeline time reaches its start. A loop or restart resets that execution flag.

`once(target, properties)` adds an animation at the current timeline time. OpenTUI removes that item after its completion.

Normal completed items and callbacks remain in `items`. Only a completed `once` animation removes itself.

## Timeline methods

| Method                                   | Behavior                                                               |
| ---------------------------------------- | ---------------------------------------------------------------------- |
| `add(target, properties, startTime = 0)` | Adds a numeric animation and returns `this`                            |
| `once(target, properties)`               | Adds a removable animation at `currentTime` and returns `this`         |
| `call(callback, startTime = 0)`          | Adds a scheduled callback and returns `this`                           |
| `sync(timeline, startTime = 0)`          | Gives this timeline scheduled control of one child                     |
| `play()`                                 | Starts or resumes. A completed timeline restarts                       |
| `pause()`                                | Pauses this timeline and all synced children                           |
| `restart()`                              | Sets time to zero, resets item flags, and starts playback              |
| `resetItems()`                           | Resets item and child flags without changing parent time or play state |
| `update(deltaTime)`                      | Evaluates synced children, items, loops, and completion                |
| `addStateChangeListener(listener)`       | Adds a play-state listener used by the engine                          |
| `removeStateChangeListener(listener)`    | Removes matching play-state listeners                                  |

`pause()` invokes `onPause` even when the timeline is already paused. It also calls `pause()` on every synced child.

`restart()` does not restore target properties immediately. The next active update calculates values from the captured initial values.

`play()` on a completed non-looping timeline calls `restart()`. `play()` on a paused incomplete timeline keeps `currentTime`.

`update()` expects elapsed milliseconds. An attached engine supplies its renderer's frame deltas, but tests and headless code can call it directly.

Use finite non-negative values for `deltaTime`, item `duration`, `loopDelay`, and start times. The current implementation does not validate these timing values.

A large delta evaluates each item once for that update. It can skip intermediate `onUpdate` and `onLoop` calls across multiple cycles.

For a looping timeline, OpenTUI keeps only the final duration overshoot. Large deltas do not replay every skipped parent loop.

## Timeline state

`Timeline` exposes these mutable public fields:

| Field          | Meaning                                                   |
| -------------- | --------------------------------------------------------- |
| `items`        | Animation and callback items                              |
| `subTimelines` | Synced-child items                                        |
| `currentTime`  | Current parent time in milliseconds                       |
| `isPlaying`    | Whether parent items advance                              |
| `isComplete`   | Whether a non-looping parent reached its cutoff           |
| `duration`     | Parent cutoff                                             |
| `loop`         | Parent loop flag                                          |
| `synced`       | Whether another timeline claimed this timeline as a child |

Prefer the methods for state transitions. Direct item-array mutation relies on internal item shapes that OpenTUI does not export.

## Sync child timelines

`parent.sync(child, startTime)` starts the child when the parent reaches that time. The child receives any overshoot from the crossing update.

Later parent updates pass their full delta to the child. Parent loops reset and restart child scheduling.

A parent pause pauses children that already started. A later parent play resumes those started children.

You can sync a timeline only once. A second claim throws exactly `Error("Timeline already synced")`.

Sync has no inverse operation. The child's `synced` flag remains `true`, even after engine unregistration.

The engine skips a registered timeline when `synced` is `true`. Its parent becomes the only normal update owner.

Current limitation: `update()` evaluates synced children before it checks whether the parent is playing. A paused parent can start a future child when another manual or engine update crosses its start time.

Do not depend on a pending child staying idle after its parent pauses. Unregister the parent during the pause, or add the child after resume.

## Engine ownership

`getTimelineEngine(renderer)` returns the engine for that renderer. It throws when the renderer is destroyed. It also restores the engine's frame callback if application code cleared renderer callbacks. Renderer destruction unregisters its timelines and detaches the engine.

The exported `engine` is the default engine. `createTimeline()` uses it when you omit the renderer. It is separate from renderer-owned engines.

`engine.attach(renderer)` moves the default engine to that renderer. React `createRoot(renderer).render()`, Solid `render()`, and Solid `testRender()` call it. The renderer from the most recent call drives timelines that you create without a renderer. While the default engine is not attached, nothing updates its timelines. Renderer destruction detaches the default engine but keeps its timelines.

Detach a renderer-owned engine before you attach it to a different renderer. A timeline can belong to only one engine at a time.

| Method                 | Behavior                                                     |
| ---------------------- | ------------------------------------------------------------ |
| `attach(renderer)`     | Installs a frame callback. Only `engine` can move renderers  |
| `detach()`             | Removes the frame callback and drops engine-owned live state |
| `register(timeline)`   | Adds one timeline and watches its state                      |
| `unregister(timeline)` | Removes one timeline and its state listener                  |
| `clear()`              | Unregisters all timelines from this engine                   |
| `update(deltaTime)`    | Updates every registered timeline that is not synced         |

`attach()` checks already-playing timelines and updates the renderer's live state. You do not need an explicit attach call when you use `getTimelineEngine(renderer)` or `createTimeline(options, renderer)`.

The engine requests live rendering when any registered, unsynced timeline is playing and incomplete. It drops live rendering after the last such timeline stops.

`createTimeline()` never unregisters its result. A completed timeline remains in the engine set until application cleanup unregisters it.

An engine can serve several components in one renderer. Do not use `clear()` for component cleanup or for one independently owned timeline.

`engine.defaults.frameRate` currently exists, but the engine does not read it. Renderer frame scheduling controls update cadence.

## Cleanup

The canonical Core cleanup sequence is:

1. Call `timeline.pause()`.
2. Call `engine.unregister(timeline)`.
3. Call `engine.detach()` only when this owner explicitly controls the whole engine.
4. Destroy the renderer through the application's normal lifecycle.

Unregister synced children that were registered separately. A child made with `new Timeline()` does not need unregistration unless you registered it.

Do not call `renderer.requestLive()` or `renderer.dropLive()` for a registered timeline. The engine balances its own live request.

See [Lifecycle and cleanup](/docs/core-concepts/lifecycle) for renderer ownership and shutdown order.

## Framework mapping

| API                     | Setup                                              | Cleanup                                                  |
| ----------------------- | -------------------------------------------------- | -------------------------------------------------------- |
| Core `new Timeline()`   | Application registers and plays it                 | Application pauses and unregisters it                    |
| Core `createTimeline()` | Factory registers and normally plays it            | Application pauses and unregisters it                    |
| React `useTimeline()`   | Hook mount effect registers and normally plays it  | Effect cleanup pauses and unregisters the mount instance |
| Solid `useTimeline()`   | Hook mount handler registers and normally plays it | Cleanup pauses and unregisters it                        |

React and Solid use the component's renderer-owned engine. Creating or destroying a root for another renderer does not move that engine or unregister its timelines.

Both hooks treat their option input as setup-time data. Later option changes do not update the mounted timeline.

### React behavior

The React hook creates its `Timeline` once through lazy `useState`. Rerenders return the same registered timeline.

The first render also captures `autoplay` and all constructor options. Later option values do not reconfigure that timeline.

The React package has a regression test that checks timeline identity across a state-driven rerender.

### Solid behavior

The Solid hook creates one timeline during component setup. Its options are not reactive after setup.

Both framework packages test that hook timelines on different renderers stay independent.

## Animation callbacks

Use `renderer.requestAnimationFrame(callback)` and `renderer.cancelAnimationFrame(handle)` for one-shot animation work. Requests and cancellation belong to that renderer. Renderer destruction cancels its pending requests.

Each pending request holds one [live request](/docs/core-concepts/renderer#live-rendering) until its callback runs or you cancel it. `requestAnimationFrame()` returns `-1` and stores nothing when the renderer cannot render, for example after destruction.

The callback receives the elapsed milliseconds since the previous frame, not a timestamp. A request made during a frame runs in the next frame.

Each frame runs animation-frame callbacks first. It then runs callbacks from `renderer.setFrameCallback()`, which include timeline engines. Both run before Core sends staged property changes to native code and runs the [native frame step](/docs/core-concepts/rendering-pipeline). Changes that these callbacks make appear in the same frame.

A thrown animation-frame callback error stops that frame. The renderer emits `render:error`, or logs the error when the event has no listener.

OpenTUI no longer installs or replaces process-global `requestAnimationFrame`, `cancelAnimationFrame`, or `window` properties. Migrate terminal applications to the renderer methods. OpenTUI does not change browser animation APIs.

After each animation-frame callback and each `setFrameCallback()` callback, the renderer yields to the shared Session scheduler. Between callbacks, that scheduler runs ready work for every Session that shares it. It parks Sessions that wait for output or host replies. It cannot interrupt a synchronous JavaScript callback. Long callbacks and large timelines still delay that work.

## Limits and unsupported features

Timeline arrays have no built-in item limit. Bound registered timelines, items, and synced children with application policy.

Each engine update is proportional to registered timelines and their items and children. Nested synced timelines add their own item work.

Completed normal items remain in the scan. Use `once()` for temporary animations that should remove themselves.

Timeline supports top-level numeric interpolation only. It does not interpolate colors, strings, units, nested paths, or keyframe arrays.

The API has no spring or physics solver. It also has no seek, item removal, unsync, per-item pause, or public reverse method.

Use `alternate` for per-cycle reversal. Use application state and a new timeline when you need a scheduling change that the API does not support.

## Next

- [Renderer](/docs/core-concepts/renderer)
- [React bindings](/docs/bindings/react)
- [Solid bindings](/docs/bindings/solid)
- [Lifecycle and cleanup](/docs/core-concepts/lifecycle)

---

### Packages Web Src Content Docs Application Apis Audio Capture (`packages/web/src/content/docs/application-apis/audio-capture.mdx`)

---
title: Audio capture and recording
description: Capture Float32 PCM, poll input devices, and record PCM16 WAV files
skill:
  entry: true
  intents: [audio-capture, microphone, pcm, recording, wav, audio-recorder]
---

# Audio capture and recording

OpenTUI captures microphone or input-device audio through the native miniaudio engine. Applications can consume Float32 PCM or record PCM16 WAV files.

Capture is independent from playback. It does not start output or the mixer, and playback lifecycle methods do not change capture state.

## Capture a PCM stream

Create capture through `audio.openCapture(options?)`. The `AudioCaptureStream` constructor is private.

```typescript
import { Audio, type AudioCaptureStream } from "@opentui/core"

const audio = Audio.create({ autoStart: false })
let capture: AudioCaptureStream | null = null

audio.on("error", (error, context) => {
  console.error(`${context.action}: ${error.message}`)
})

function reportPcm(pcm: Float32Array, channels: number): void {
  console.log(`received ${pcm.length / channels} frames`)
}

try {
  const activeCapture = await audio.openCapture({
    channels: 1,
    chunkFrames: 2048,
  })
  capture = activeCapture

  activeCapture.on("error", (error, context) => {
    console.error(`${context.action}: ${error.message}`)
  })

  const consumption = (async () => {
    for await (const pcm of activeCapture.readable) {
      reportPcm(pcm, activeCapture.channels)
    }
  })()

  await new Promise((resolve) => setTimeout(resolve, 1000))
  activeCapture.stop()
  await consumption
  await activeCapture.closed
} finally {
  if (capture != null && capture.state !== "stopped" && capture.state !== "errored" && capture.state !== "disposed") {
    capture.dispose()
  }
  await capture?.closed
  audio.dispose()
}
```

`Audio` and `AudioCaptureStream` inherit Node's `EventEmitter`. Attach the `Audio` error listener before `openCapture()`.

Attach the stream error listener immediately after setup resolves and before reading. Setup failures reject because no stream exists for a listener yet.

An unhandled later `error` event throws. A parent method that returns `false` or `null` can emit and throw before it returns.

## Capture options

`openCapture()` accepts these options:

| Option           | Default                 | Effect                                               |
| ---------------- | ----------------------- | ---------------------------------------------------- |
| `channels`       | `1`                     | Samples in each interleaved frame                    |
| `capacityFrames` | `audio.sampleRate`      | Native ring capacity, equal to one second by default |
| `chunkFrames`    | `2048`                  | Frames in each normal stream chunk                   |
| `startOptions`   | Capture native defaults | Low-level input-device options                       |
| `signal`         | None                    | Disposes capture when aborted                        |

Frame counts and channel counts must be finite positive unsigned 32-bit integers. `chunkFrames` cannot exceed `capacityFrames`.

The product of `chunkFrames` and `channels` must also fit an unsigned 32-bit integer. Native channel and ring-allocation limits can reject larger valid integers.

Capture always uses `audio.sampleRate`. Create another `Audio` engine to use a different sample rate.

`recordToFile()` accepts the same options. WAV recording limits `channels` to `1` or `2`.

### Capture start-option detail

Capture has a special default for `noFixedSizedCallback`. The wrapper sets the native value to `true` when that field
is undefined. This applies when `startOptions` is omitted and when it is `{}`. Pass
`startOptions: { noFixedSizedCallback: false }` to select `false` explicitly.

See [Low-level start options](/docs/core-concepts/audio#low-level-start-options) for the complete field list.

## PCM stream behavior

`readable` is a `ReadableStream<Float32Array>` with `highWaterMark: 0`. OpenTUI reads native PCM only when a consumer requests data.

While capture runs, the stream waits for a complete `chunkFrames` block. Each normal array contains `chunkFrames * channels` valid samples.

PCM uses interleaved 32-bit floating-point samples. Values are nominally in `[-1, 1]`, and one frame contains one sample for each channel.

The stream does not build a JavaScript PCM queue. It keeps one in-progress chunk and uses the native ring as its bounded buffer.

The native input callback never waits for JavaScript. When the ring is full, it keeps older frames and drops the newest complete frames.

`getStats().framesDropped` counts that loss. A nonzero value means the captured timeline has gaps.

`framesRead` counts frames removed from the native ring. It includes frames discarded during an unlocked graceful stop.

Thus, `framesRead` does not always equal frames delivered to application code.

## Stop, cancel, and dispose

`stop()` stops the input device and starts a graceful drain. Its state changes to `stopping` until unread PCM is handled.

When a consumer holds the readable lock, the stream emits every remaining full chunk. It then emits one exact-length short final chunk when needed.

The consumer must continue reading during this drain. A locked reader that stops pulling can keep graceful shutdown pending.

Release the reader lock when the consumer will not continue. The stream then switches to its no-reader drain.

Without a readable lock, OpenTUI discards unread PCM in bounded batches. It yields to the event loop after each 32 chunks.

The discard updates native `framesRead`, releases capture ownership, and lets `closed` resolve without consumer demand.

Canceling `readable`, calling `dispose()`, aborting `signal`, or disposing the owning `Audio` skips the graceful drain. The terminal state becomes `disposed`.

`closed` resolves after cleanup and the one terminal event. It never rejects.

## Select an input device

List and select an input before capture starts.

```typescript
const devices = audio.listCaptureDevices() ?? []
const selected = devices.find((device) => device.isDefault) ?? devices[0]

if (selected != null && !audio.selectCaptureDevice(selected.index)) {
  throw new Error(`Could not select ${selected.name}`)
}
```

Each `AudioCaptureDevice` has `index`, `name`, and `isDefault` fields. An empty array means enumeration found no inputs.

`null` means enumeration failed. Device indices belong to the latest enumeration and can change after hot-plug events.

`clearCaptureDeviceSelection()` removes the explicit selection. A later start then asks the backend for its default input.

`selectCaptureDevice()` fails while a capture device is open. Stop capture before selecting another input.

An owned stream or recorder also blocks `clearCaptureDeviceSelection()`. During direct capture, clearing changes only the selection for a later start.

## Exclusive capture ownership

One `Audio` engine permits one capture owner. An active `AudioCaptureStream` or `AudioRecorder` owns the native capture ring exclusively.

A second `openCapture()` rejects with `AudioCaptureStreamError`. A second `recordToFile()` rejects with `AudioRecorderError` after it cleans its temporary file.

These direct methods fail while a stream or recorder owns capture:

| Direct method                   | Owned-capture result                    |
| ------------------------------- | --------------------------------------- |
| `startCapture()`                | Returns `false` and emits `Audio.error` |
| `readCaptureFrames()`           | Returns `null` and emits `Audio.error`  |
| `stopCapture()`                 | Returns `false` and emits `Audio.error` |
| `selectCaptureDevice()`         | Returns `false` and emits `Audio.error` |
| `clearCaptureDeviceSelection()` | Returns `void` and emits `Audio.error`  |

`listCaptureDevices()`, `isCapturing()`, and `getCaptureStats()` remain available. Their information can describe the active owner.

A failed native stop can retain exclusive ownership after the child enters `errored`. A later `dispose()` call retries cleanup.

The parent also retries child cleanup during `audio.dispose()`. Do not create another owner until cleanup releases the first one.

## Poll capture directly

Use the direct API when application code controls the polling cadence.

```typescript
audio.on("error", (error, context) => {
  console.error(`${context.action}: ${error.message}`)
})

if (!audio.startCapture({ channels: 2, capacityFrames: 48_000 })) {
  throw new Error("Could not start capture")
}

const result = audio.readCaptureFrames(1024)
if (result != null) {
  const channels = audio.getCaptureStats()?.channels ?? 2
  const validSamples = result.frames.subarray(0, result.framesRead * channels)
  console.log(validSamples.length)
}

audio.stopCapture()
```

### Direct method reference

| Method                          | Result and behavior                             |
| ------------------------------- | ----------------------------------------------- |
| `listCaptureDevices()`          | Returns `AudioCaptureDevice[] \| null`          |
| `selectCaptureDevice(index)`    | Selects one current index and returns `boolean` |
| `clearCaptureDeviceSelection()` | Clears explicit selection and returns `void`    |
| `startCapture(options?)`        | Starts input and returns `boolean`              |
| `isCapturing()`                 | Reports whether the input device still runs     |
| `readCaptureFrames(frameCount)` | Returns `{ frames, framesRead } \| null`        |
| `getCaptureStats()`             | Returns `AudioCaptureStats \| null`             |
| `stopCapture()`                 | Stops the input device and returns `boolean`    |

`readCaptureFrames()` consumes up to `frameCount` frames. `frameCount` cannot exceed the configured ring capacity.

Each call allocates `frameCount * channels` samples. Only the first `framesRead * channels` samples contain captured data.

`stopCapture()` preserves unread PCM for later direct reads. A later successful `startCapture()` replaces the ring and resets all counters.

While capture runs, `startCapture()` is idempotent when no explicit setting differs. Matching `channels` and `capacityFrames` also succeed without a restart.

Any supplied `startOptions` requests reconfiguration and fails while capture runs. Stop capture before changing options.

Successful transitions emit parent `captureStarted` and `captureStopped` events. An observed external device stop can also emit `captureStopped`.

## Record a WAV file

Create recording through `audio.recordToFile(filePath, options?)`. The `AudioRecorder` constructor is private.

```typescript
import { Audio, type AudioRecorder } from "@opentui/core"

const audio = Audio.create({ autoStart: false })
const recording = "recording.wav"
let recorder: AudioRecorder | null = null

audio.on("error", (error, context) => {
  console.error(`${context.action}: ${error.message}`)
})

try {
  const activeRecorder = await audio.recordToFile(recording, {
    channels: 1,
  })
  recorder = activeRecorder

  activeRecorder.on("error", (error, context) => {
    console.error(`${context.action}: ${error.message}`)
  })

  await new Promise((resolve) => setTimeout(resolve, 1000))
  activeRecorder.stop()
  await activeRecorder.closed

  if (activeRecorder.state === "errored") {
    throw new Error("Recording failed")
  }
} finally {
  if (
    recorder != null &&
    recorder.state !== "stopped" &&
    recorder.state !== "errored" &&
    recorder.state !== "disposed"
  ) {
    recorder.dispose()
  }
  await recorder?.closed
  audio.dispose()
}
```

`AudioRecorder` also inherits `EventEmitter`. Attach its error listener immediately after setup and before waiting or calling a method.

Setup errors reject `recordToFile()`. Later write, capture, finalization, publication, and cleanup errors emit `error`.

## WAV format and filesystem behavior

The recorder writes little-endian PCM16 WAV. Its `format` property is the literal `"wav"`.

Float samples clamp to `[-1, 1]` before conversion. The recorder writes chunks sequentially and keeps one capture read pending during each file write.

The destination path must be a nonempty string without NUL bytes. Its parent directory must already exist.

OpenTUI creates the temporary file in the destination directory with exclusive creation. It tries at most 64 candidate names and never opens an existing candidate.

Recording first writes a placeholder header to that temporary file. It does not modify an existing destination during capture.

`stop()` stops input, drains all buffered frames, and writes the final WAV header. It then syncs and closes the temporary file.

After those steps, OpenTUI calls a same-directory filesystem `rename()` from the temporary path to the destination. The host filesystem defines replacement and rename guarantees.

OpenTUI adds no guarantee beyond that rename behavior. Code that needs a specific durability or replacement contract must account for the target filesystem.

The rename call is the publication point of no return. After rename starts, recorder disposal, signal abort, and parent disposal let it finish.

Before rename starts, disposal tries to remove the temporary file. Header, sync, close, or capture failures do not publish that file.

A cleanup failure reports action `destroy` and can retain the file handle or temporary path. A later `dispose()` retries those retained resources.

Dropped capture frames are terminal for a recorder. OpenTUI reports an error and does not publish an incomplete recording.

Classic RIFF limits the audio data chunk to exactly `4,294,967,259` bytes. The recorder does not support RF64 or compressed WAV data.

## Stream and recorder members

### `AudioCaptureStream`

| Member                   | Behavior                                                  |
| ------------------------ | --------------------------------------------------------- |
| `readable`               | Demand-driven stream of interleaved `Float32Array` chunks |
| `sampleRate`, `channels` | Captured PCM format                                       |
| `chunkFrames`            | Normal requested chunk size                               |
| `state`                  | Current capture-stream state                              |
| `getStats()`             | Refreshes and returns capture statistics                  |
| `stop()`                 | Stops input and starts a graceful drain                   |
| `dispose()`              | Stops without a graceful drain                            |
| `closed`                 | Resolves after cleanup and terminal event delivery        |

Capture-stream state is `initializing`, `capturing`, `stopping`, `stopped`, `errored`, or `disposed`.

Capture-stream statistics contain `state`, `sampleRate`, `channels`, `capacityFrames`, `bufferedFrames`, and `bufferedDurationMs`. They also contain `framesReceived`, `framesRead`, and `framesDropped`.

### `AudioRecorder`

| Member                   | Behavior                                            |
| ------------------------ | --------------------------------------------------- |
| `filePath`, `format`     | Destination path and literal `"wav"`                |
| `sampleRate`, `channels` | Recorded PCM format                                 |
| `state`                  | Current recorder state                              |
| `getStats()`             | Returns capture, file, and duration statistics      |
| `stop()`                 | Drains, finalizes, syncs, closes, and renames       |
| `dispose()`              | Aborts before rename and removes the temporary file |
| `closed`                 | Resolves after cleanup and terminal event delivery  |

Recorder state is `initializing`, `recording`, `stopping`, `stopped`, `errored`, or `disposed`.

Recorder statistics add `framesWritten`, `dataBytesWritten`, and `durationMs` to capture statistics. `durationMs` derives from written frames.

## Events and error actions

Both child classes emit asynchronous, mutually exclusive terminal events:

| Class                | Events                         |
| -------------------- | ------------------------------ |
| `AudioCaptureStream` | `stopped`, `error`, `disposed` |
| `AudioRecorder`      | `stopped`, `error`, `disposed` |

Each `error` event receives the typed error and `{ action, status? }`. `closed` resolves after the event listener runs, even if that listener throws.

The exact `AudioCaptureStreamAction` values are `start`, `read`, `stop`, `stats`, and `destroy`.

The exact `AudioRecorderAction` values are `open`, `start`, `read`, `write`, `stop`, `finalize`, `publish`, `stats`, and `destroy`.

Invalid options throw `TypeError` or `RangeError` before native work. A pre-start abort rejects with `AbortError`.

Capture-stream setup failures reject with `AudioCaptureStreamError`. Recorder setup and cleanup failures reject with `AudioRecorderError`.

Later failures error the readable when applicable and emit the child `error` event. Unhandled error events throw through `EventEmitter`.

## Permissions and signal limits

The application that starts OpenTUI must have microphone permission. On macOS, grant access to the terminal application and restart it fully.

A packaged macOS application needs its own permission. Windows privacy controls can block microphone access for desktop applications.

Linux capture uses PulseAudio or ALSA. PipeWire works through its PulseAudio compatibility layer, subject to device permissions.

Headless sessions, containers, sandboxes, and continuous integration systems often have no input device. Enumeration can return `null` or an empty array.

A denied or unavailable input can reject setup, return `false`, or deliver clocked digital silence. Advancing frame counters does not prove that a signal exists.

OpenTUI capture does not provide compressed recording, system-output loopback, echo cancellation, or JavaScript callbacks on the native audio thread.

Run compression and signal processing outside the native callback path. Use platform-specific input facilities for system loopback.

## Next

- [Audio](/docs/core-concepts/audio)
- [Streaming audio](/docs/application-apis/audio-streaming)
- [Lifecycle and cleanup](/docs/core-concepts/lifecycle)

---

### Packages Web Src Content Docs Application Apis Audio Streaming (`packages/web/src/content/docs/application-apis/audio-streaming.mdx`)

---
title: Streaming audio
description: Play bounded PCM, MP3, or FLAC streams through the native audio mixer
skill:
  entry: true
  intents: [audio-streaming, audio-stream, pcm, f32le, radio, mp3, flac, icy, backpressure, reconnect]
---

# Streaming audio

OpenTUI streams PCM, MP3, or FLAC bytes into the native audio mixer. Streaming starts before the complete source arrives.

Use streaming for generated audio, external decoders, long media, or internet radio. Use [loaded sounds](/docs/core-concepts/audio#load-sounds) for short, reusable audio.

## Play decoded PCM

Pass `format: "pcm"`, `sampleFormat: "f32le"`, `sampleRate`, and `channels` to `audio.playStream()`. The source is a `ReadableStream<Uint8Array>` or `AsyncIterable<Uint8Array>`, just like encoded audio. Playback returns the same `AudioStream` with the same controls, metadata, statistics, and lifecycle.

```typescript
import { Audio, type AudioStream } from "@opentui/core"

const audio = Audio.create({ autoStart: false })
audio.on("error", (error) => console.error(error))
let stream: AudioStream | undefined

async function* tone(): AsyncGenerator<Uint8Array> {
  const framesPerChunk = 441
  const chunk = new Uint8Array(framesPerChunk * 4)
  const view = new DataView(chunk.buffer)
  for (let offset = 0; offset < 44100; offset += framesPerChunk) {
    for (let frame = 0; frame < framesPerChunk; frame++) {
      const sample = Math.sin((2 * Math.PI * 440 * (offset + frame)) / 44100) * 0.15
      view.setFloat32(frame * 4, sample, true)
    }
    yield chunk
    // Reuse the buffer only after the generator resumes.
  }
}

try {
  if (!audio.start()) throw new Error("No playback device is available")
  stream = await audio.playStream(tone(), {
    format: "pcm",
    sampleFormat: "f32le",
    sampleRate: 44100,
    channels: 1,
    buffer: { capacityMs: 500, startupMs: 20, resumeMs: 20 },
  })
  stream.on("error", (error) => console.error(error))
  await stream.closed
} finally {
  try {
    stream?.dispose()
    await stream?.closed
  } finally {
    audio.dispose()
  }
}
```

### PCM format and ownership

- Each sample is a finite 32-bit float encoded in little-endian byte order, nominally in `[-1, 1]`. Input is interleaved: mono is one sample per frame; stereo is left, right, left, right.
- `channels` is `1` or `2`. `sampleRate` is an integer from `8000` through `192000` Hz. The engine rate must be in that range too. Native conversion duplicates mono into stereo and resamples to the engine rate, preserving conversion history between chunks.
- Byte chunks may split samples or frames at any boundary. OpenTUI carries incomplete input between reads. EOF with a partial final frame is an error. Empty chunks are accepted.
- Do not mutate, transfer, or detach a chunk while OpenTUI consumes it. An async generator may reuse its yielded buffer when the generator resumes. A `ReadableStream` producer must not reuse a queued buffer before it is consumed. Shared backing memory is rejected.

For generated or captured `Float32Array` samples, encode bytes explicitly rather than relying on the machine's native byte order:

```typescript
async function* pcmBytes(source: AsyncIterable<Float32Array>): AsyncGenerator<Uint8Array> {
  for await (const samples of source) {
    const bytes = new Uint8Array(samples.length * 4)
    const view = new DataView(bytes.buffer)
    for (let i = 0; i < samples.length; i++) {
      view.setFloat32(i * 4, samples[i], true)
    }
    yield bytes
  }
}
```

Pass `pcmBytes(source)` to `playStream()` with the source's sample rate and channel count. For direct capture reads, encode only the first `framesRead * channels` samples.

An external decoder's `f32le` byte pipe can be passed directly as the source. Applications own decoder processes and must stop them on cancellation. OpenTUI cancels the reader or returns the iterator; the source must release its resources in `cancel()`, iterator cleanup, or a connector's `close()` callback. Make pending external reads respond to cancellation too.

### PCM buffering and completion

PCM uses the shared buffer defaults: `capacityMs: 2000`, `startupMs: 1000`, and `resumeMs: 1000`. The example explicitly lowers them to `500`, `20`, and `20` milliseconds for low latency.

PCM uses the same native worker and 256 KiB input queue as MP3 and FLAC. The worker converts input in batches of at most 2048 frames into the output ring. A full output ring pauses the worker; a full input queue backpressures the source. Accepted bytes need not already be converted. Native code retains no JavaScript pointers.

Source EOF automatically flushes the demuxer and signals the worker to finish conversion, including its tail, then drains queued audio. Await `stream.closed` for terminal cleanup; there is no separate write or end call. `closed` never rejects, so attach an `error` listener to observe playback failures. It resolves after cancellation and errors too.

Start the audio device first, or call `audio.startMixer()` and consume `audio.mixFrames()` regularly. A stopped mixer cannot drain queued audio. Completion means the mixer consumed the stream, not that the device physically presented the final sample.

Use the existing `setVolume()`, `setPan()`, `setGroup()`, and `dispose()` methods. Input format stays fixed for the stream's lifetime. For a track switch or rate change, dispose the old stream and open another source. Streaming has no public pause, resume, clear, write, or end methods.

The same PCM options work with `playStreamUrl()` and `playStreamSource()`, including custom demuxers where supported. PCM reconnect is not supported: passing `reconnect` rejects before the source opens.

PCM uses the shared [statistics](#members-state-and-statistics): `bytesReceived` counts accepted input bytes, including a pending partial frame; `framesDecoded` counts converted engine-rate output, including the conversion tail. There is no separate input-frame counter.

Run the [generated-tone example](https://github.com/anomalyco/opentui/blob/main/packages/examples/src/pcm-playback.ts) from the examples package with `bun src/pcm-playback.ts`. Add `--headless` to exercise conversion and mixing without an output device.

## Play a URL

The engine does not start output when it creates a stream. Start a playback device first, or consume headless output with `mixFrames()`.

```typescript
import { Audio, AudioStreamError, type AudioStream } from "@opentui/core"

const audio = Audio.create({ autoStart: false })
const controller = new AbortController()
const stopTimer = setTimeout(() => controller.abort(), 30_000)
let stream: AudioStream | null = null

function reportSetupError(error: unknown): void {
  if (error instanceof AudioStreamError) {
    console.error(`${error.context.action}: ${error.message}`)
  } else if (!(error instanceof DOMException && error.name === "AbortError")) {
    throw error
  }
}

audio.on("error", (error, context) => {
  console.error(`${context.action}: ${error.message}`)
})

try {
  if (!audio.start()) throw new Error("No playback device is available")

  stream = await audio.playStreamUrl("https://localhost/radio", {
    signal: controller.signal,
    reconnect: { maxRetries: 5 },
  })

  stream.on("error", (error, context) => {
    console.error(`${context.action}: ${error.message}`)
  })

  await stream.closed
} catch (error) {
  reportSetupError(error)
} finally {
  clearTimeout(stopTimer)
  if (stream != null && stream.state !== "ended" && stream.state !== "errored" && stream.state !== "disposed") {
    stream.dispose()
  }
  await stream?.closed
  audio.dispose()
}
```

`Audio` and `AudioStream` inherit Node's `EventEmitter`. Attach the `Audio` error listener before `start()` or stream setup.

Attach the `AudioStream` error listener immediately after setup resolves. Later stream errors emit asynchronously.

Stream setup errors reject the entry-method promise because the stream is not available for a listener yet. An unhandled later `error` event throws.

A control method can return `false` and schedule an `error` event. Without a listener, the event can throw after the
caller receives `false`.

## Choose an entry method

The three entry methods own different source policies.

| Method                                  | Source and ownership                                                         |
| --------------------------------------- | ---------------------------------------------------------------------------- |
| `playStream(source, options?)`          | Reads one `ReadableStream<Uint8Array>` or `AsyncIterable<Uint8Array>` to EOF |
| `playStreamUrl(url, options?)`          | Owns Fetch, HTTP validation, ICY metadata, and optional reconnects           |
| `playStreamSource(connector, options?)` | Opens custom connections and creates a demuxer for each connection           |

`playStream()` is one-shot. It does not accept request, reconnect, content-type, or metadata-encoding options.

`playStreamUrl()` accepts a string or `URL`. It creates a new response body and ICY demuxer for each reconnect.

`playStreamSource()` accepts an `AudioStreamConnector<I>`. Its `demuxer(info)` callback can create transport-specific framing for each connection.

All methods support `format: "pcm"`, `format: "mp3"`, and `format: "flac"`. The default is `"mp3"`.

The promise resolves after native playback setup is ready. Encoded streams must probe their decoder first; PCM already has an explicit input format. The stream is usually still in `buffering` state at that time.

## Shared options

| Option              | Default   | Constraint or effect                                           |
| ------------------- | --------- | -------------------------------------------------------------- |
| `format`            | `"mp3"`   | `"pcm"`, `"mp3"`, or `"flac"`                                  |
| `sampleFormat`      | None      | Required for PCM; only `"f32le"`                               |
| `sampleRate`        | None      | Required for PCM; integer from `8000` through `192000` Hz      |
| `channels`          | None      | Required for PCM; `1` or `2`                                   |
| `volume`            | `1`       | Native playback clamps it to `0..4`                            |
| `pan`               | `0`       | Native playback clamps it to `-1..1`                           |
| `groupId`           | `0`       | Must be an unsigned 32-bit ID for a group in the owning engine |
| `buffer.capacityMs` | `2000`    | Decoded PCM capacity                                           |
| `buffer.startupMs`  | `1000`    | Initial playback threshold                                     |
| `buffer.resumeMs`   | `1000`    | Resume threshold after an underrun                             |
| `maxProbeBytes`     | `1048576` | Maximum encoded bytes inspected during startup; unused for PCM |
| `signal`            | None      | Cancels setup, source work, reconnect delays, and playback     |

The three buffer durations and `maxProbeBytes` must be positive unsigned 32-bit integers. Startup and resume durations cannot exceed capacity.

`sampleFormat`, `sampleRate`, and `channels` are PCM-only options. Supplying them for an encoded format rejects setup.

All formats produce stereo interleaved Float32 PCM at `audio.sampleRate`. PCM conversion requires an engine rate from `8000` through `192000` Hz. The maximum output capacity is `268,435,452` stereo frames.

Each stream uses one of the engine's 32 combined loaded-voice and stream slots.

## Start, underrun, and EOF behavior

Decoding or conversion fills the PCM ring before playback crosses `startupMs`. Calls to `mixFrames()` return silence for that stream below the initial threshold.

After playback starts, starvation changes the stream to `buffering` and increments `underruns` once. Playback resumes when PCM reaches `resumeMs`.

If input reaches EOF with some PCM, playback can start or resume below either threshold. It drains the final frames before the stream ends.

Clean source EOF first flushes the demuxer. OpenTUI then finishes decoding or flushes the PCM converter and waits for output to drain.

`ended` and `closed` can remain pending when no output consumes the decoded ring. Call `start()`, or call `startMixer()` and `mixFrames()` at a regular cadence.

During a reconnect, a stream that already started can play PCM left in its decoded ring. Controls, counters, and the native voice slot remain in the same stream session.

## Buffering and backpressure

OpenTUI bounds these internal layers:

| Layer                    | Bound                                                  |
| ------------------------ | ------------------------------------------------------ |
| Native input bytes       | `256 KiB` per stream                                   |
| Native output PCM        | `buffer.capacityMs`, up to `268,435,452` stereo frames |
| Native PCM conversion    | `2048` input frames per batch plus one partial frame   |
| JavaScript source demand | One active `read()` or iterator `next()`               |

OpenTUI copies the current source chunk into the native input queue before it requests the next chunk. A full input queue applies backpressure to that chunk. `buffer.capacityMs` bounds the output ring, not audio also waiting in the input queue or worker batch.

A blocked native write retries at 5 ms intervals. This polling exists only while that write has no space.

An empty input chunk yields with a zero-delay timer. This prevents an endless empty source from blocking cancellation.

OpenTUI does not limit the size of one supplied chunk. It also does not limit one custom demuxer output or the number of outputs in its iterable.

Source implementations can maintain their own queues before OpenTUI calls `read()` or `next()`. Those external queues are outside OpenTUI's bounds.

Readiness and EOF use short-lived polling. The stream does not keep a persistent idle statistics timer.

## URL response policy

`playStreamUrl()` uses `contentTypePolicy: "validate"` by default.

| Format | Accepted `Content-Type` values                                                       |
| ------ | ------------------------------------------------------------------------------------ |
| MP3    | `audio/mpeg`, `audio/mp3`, `application/mp3`, `application/octet-stream`, or missing |
| FLAC   | `audio/flac`, `audio/x-flac`, `application/octet-stream`, or missing                 |
| PCM    | `audio/pcm`, `application/octet-stream`, or missing                                  |

Validation ignores case and parameters such as `charset=binary`. It evaluates every initial and replacement response before native stream allocation or byte ingestion.

Use `contentTypePolicy: "ignore"` when the server has an incorrect label. Audio input validation still applies. For PCM, headers do not replace the required sample format, rate, and channel options.

A callback policy receives `{ format, contentType, status, url }` and must return a boolean. The `url` is the effective response URL after redirects.

The `request` option accepts `RequestInit` except `body` and `signal`. OpenTUI also strips those two fields at runtime.

Use the stream-level `signal` option for cancellation. Request headers and other request options apply again to every reconnect.

## ICY metadata

URL streams add `Icy-MetaData: 1` unless `request.headers` already contains that header. Set the header to `0` to disable negotiation explicitly.

OpenTUI copies every `icy-*` response header into an immutable metadata snapshot. Header names become lowercase.

A non-negative safe integer `icy-metaint` controls in-band framing. A positive value enables metadata blocks, while zero exposes headers without framing.

OpenTUI does not infer framing from a URL, content type, server name, or station header. An invalid or ambiguous interval rejects the response.

`getMetadata()` returns the latest `{ format: "icy", headers, fields }` snapshot or `null`. In-band field names keep their source case.

Unknown fields remain in the snapshot. Zero-length blocks, invalid field text, and repeated equivalent fields do not emit a change.

ICY does not define one text encoding. OpenTUI defaults to ISO-8859-1, which the Encoding Standard decodes as `windows-1252`.

Set `metadataEncoding` when a station uses another encoding. OpenTUI validates the encoding before it sends a request.

Metadata is untrusted text. Remove terminal control characters before display, and do not open a metadata URL without validation.

Metadata follows response ingestion, not audible playback. A title can arrive before the related decoded PCM becomes audible.

Events are asynchronous and rapid changes can coalesce. `getMetadata()` always returns the latest snapshot.

Metadata found during setup remains available after the promise resolves. OpenTUI schedules its event so an immediate listener can observe it.

During reconnect, the old snapshot remains until the replacement demuxer starts. Replacement ICY fields replace the old fields.

A replacement response without ICY headers sets metadata to `null` and emits `metadata` with `null`.

## Reconnect URL streams

Reconnect is opt-in for MP3 and FLAC. Pass a `reconnect` object to enable it. PCM rejects this option before opening a URL or custom source.

| Option           | Default    | Constraint or effect                    |
| ---------------- | ---------- | --------------------------------------- |
| `maxRetries`     | `Infinity` | Consecutive retries for one outage      |
| `initialDelayMs` | `1000`     | First retry delay                       |
| `maxDelayMs`     | `15000`    | Delay cap                               |
| `backoffFactor`  | `2`        | Exponential factor, at least `1`        |
| `retryOnEnd`     | `false`    | Reconnect after clean EOF and PCM drain |
| `retry`          | None       | Overrides retry classification or delay |

Use a finite `maxRetries` and an `AbortSignal` for unattended streams. The default retry count can keep initial setup pending without a limit.

URL streams retry these cases by default:

- Fetch failures
- A successful response without a body
- HTTP `408`, `425`, or `429`
- HTTP status values from `500` through `599`
- A response body that fails before clean EOF

Other HTTP responses and content-type failures stop by default. Decoder failures, native creation failures, and demuxer `push()` failures are terminal.

Valid `Retry-After` seconds or dates replace normal backoff, up to `maxDelayMs`.

`reconnect.retry(error, context)` receives the next `attempt`, `maxRetries`, and a `phase` of `"connect"` or `"read"`.

Return `false` to stop. Return `{}` to keep a transport delay or normal backoff.

Return `{ delayMs }` to set a finite non-negative integer delay. OpenTUI caps that value at `maxDelayMs`.

The callback can override default URL response classification. OpenTUI does not call it for a clean EOF retry from `retryOnEnd`.

Initial retries occur while `playStreamUrl()` is pending. They call `retry`, but they do not emit `reconnecting` because callers do not have the stream yet.

After a replacement decoder becomes ready, the consecutive attempt number and retry budget reset. A later outage starts at attempt `1` again.

`getStats().reconnectAttempts` remains cumulative. It includes retries that occurred during initial setup and all later outages.

## Use a custom connector

An `AudioStreamConnector<I>` opens one connection at a time:

```typescript
interface AudioStreamConnectContext {
  readonly signal: AbortSignal
  readonly attempt: number
}

interface AudioStreamConnection<I> {
  readonly body: ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>
  readonly info: I
  close?(): void | Promise<void>
}

interface AudioStreamConnector<I> {
  connect(context: AudioStreamConnectContext): Promise<AudioStreamConnection<I>>
}
```

The initial `attempt` is `0`. Each consecutive reconnect gets the next value until a decoder becomes ready.

Connector and body-read failures are retryable by default when `reconnect` exists. The same retry options and callback rules apply.

The `info` value goes to the connection's demuxer factory. Return `undefined` when the transport has no connection metadata.

## Use a custom demuxer

`playStream()` accepts `demuxer: () => AudioStreamDemuxer<M>`. `playStreamSource()` accepts `demuxer: (info) => AudioStreamDemuxer<M> | null`.

Each connector attempt receives a fresh demuxer. A demuxer has this interface:

```typescript
type AudioStreamDemuxOutput<M> = { type: "audio"; data: Uint8Array } | { type: "metadata"; metadata: M | null }

interface AudioStreamDemuxer<M> {
  readonly initialMetadata: M | null
  push(chunk: Uint8Array): Iterable<AudioStreamDemuxOutput<M>>
  flush(): Iterable<AudioStreamDemuxOutput<M>>
  abort?(reason: unknown): void
}
```

OpenTUI consumes outputs in iterable order. It sends audio outputs to the decoder or PCM converter and publishes metadata outputs. For `format: "pcm"`, demuxer audio outputs contain `f32le` bytes and may split samples or frames.

Clean EOF calls `flush()` and consumes all flush outputs before native EOF. An interruption calls `abort()` instead of `flush()`.

A `push()` exception is terminal. A `flush()` exception means truncated input and follows the reconnect read policy.

Custom metadata snapshots should be immutable. The demuxer controls equivalent-update suppression for its metadata type.

Use `createIcyStreamDemuxer()` for ICY framing on a non-HTTP transport. It accepts `metadataInterval`, `metadataEncoding`, and optional `headers`.

The public ICY demuxer uses the same `windows-1252` default. It copies headers and changes their names to lowercase.

## Connection cleanup

OpenTUI waits for an active resource acquisition to finish before it cleans that connection.

Cleanup first calls `demuxer.abort()` when the demuxer did not finish. It then cancels the reader or returns the async iterator.

OpenTUI calls `connection.close()` after it requests source release. It waits for source and connection cleanup together.

A reconnect waits for the previous connection cleanup before it opens the replacement. This prevents two custom connections from overlapping.

Terminal shutdown gives uncooperative cleanup 50 ms of grace. The `closed` promise can then resolve while an external cleanup promise remains pending.

OpenTUI calls `close()` at most once for each returned connection. It ignores exceptions from cleanup callbacks.

`dispose()` can throw `AudioStreamError` with action `destroy` when native close fails. The stream retains its native ownership so a later `dispose()` can retry.

## Members, state, and statistics

| `AudioStream` member | Behavior                                                       |
| -------------------- | -------------------------------------------------------------- |
| `format`             | Resolved `"pcm"`, `"mp3"`, or `"flac"` value                   |
| `state`              | Latest cached lifecycle snapshot                               |
| `getStats()`         | Refreshes native state and returns current or final statistics |
| `getMetadata()`      | Returns the latest metadata snapshot or `null`                 |
| `setVolume(volume)`  | Sets volume and returns `boolean`                              |
| `setPan(pan)`        | Sets pan and returns `boolean`                                 |
| `setGroup(groupId)`  | Moves the stream and returns `boolean`                         |
| `dispose()`          | Cancels source work, reconnects, and native playback           |
| `closed`             | Resolves after terminal event delivery and bounded cleanup     |

`state` can be `initializing`, `buffering`, `playing`, `reconnecting`, `ended`, `errored`, or `disposed`. Read `getStats()` for an explicit native refresh.

Controls remain active across reconnects. They return `false` after terminal cleanup or when native control fails.

`getStats()` returns these fields:

| Field                              | Meaning                                        |
| ---------------------------------- | ---------------------------------------------- |
| `state`                            | Refreshed lifecycle state                      |
| `sampleRate`, `channels`           | Output format: engine sample rate and stereo   |
| `bufferedFrames`, `capacityFrames` | Current output ring use and capacity           |
| `bufferedDurationMs`               | Current buffered output duration               |
| `bytesReceived`                    | Audio bytes accepted from demuxer outputs      |
| `framesDecoded`, `framesPlayed`    | Cumulative produced and consumed output frames |
| `underruns`                        | Starvation transitions after playback starts   |
| `reconnectAttempts`                | Cumulative reconnect attempts                  |

`bytesReceived` excludes framing and metadata bytes. For PCM it includes bytes held in a pending partial frame. Integer-divide by `channels * 4` using the input channel count to recover the number of complete input frames received.

`framesDecoded` means decoded frames for encoded formats and converted output frames for PCM. PCM output includes conversion delay and the flushed filter tail. `framesPlayed` excludes underrun silence. Frame counters are `bigint` and use the engine rate; buffered duration excludes device buffers. Final statistics and metadata remain readable after natural completion.

## Events and errors

| Event          | Payload and meaning                                  |
| -------------- | ---------------------------------------------------- |
| `metadata`     | Latest metadata value or `null`                      |
| `reconnecting` | `{ attempt, delayMs, maxRetries, error }`            |
| `ended`        | Clean source and decoded PCM completion              |
| `error`        | `(error, { action, status?, errorCode?, attempt? })` |
| `disposed`     | Explicit, signal, or parent disposal                 |

Terminal `ended`, `error`, and `disposed` events are asynchronous and mutually exclusive. `closed` resolves after the terminal listener runs, even if that listener throws.

`closed` never rejects. An unhandled `error` event still throws through normal `EventEmitter` behavior.

The exact `AudioStreamAction` values are:

- `fetch`
- `response`
- `source`
- `demuxer`
- `create`
- `write`
- `end`
- `restart`
- `stats`
- `decoder`
- `destroy`
- `setVolume`
- `setPan`
- `setGroup`

Transport, response, demuxer, native creation, and decoder setup failures reject with `AudioStreamError`. JavaScript option validation rejects with `TypeError` or `RangeError`.

Invalid source chunks reject or emit a `TypeError`. Cancellation uses an `AbortError` and becomes `disposed` after setup.

The worker detects non-finite PCM samples and EOF with a partial input frame; either terminates playback with `AudioStreamError`. Accepting input bytes does not mean they have passed conversion. PCM follows the same setup rejection and asynchronous terminal-event rules as encoded streams.

Starting a stream after its owning `Audio` was disposed rejects with `Error`. Disposing the owner also disposes pending and active streams.

## Unsupported operations

Built-in streaming decoders do not support WAV, AAC, Ogg, Opus, or HLS playlists. Applications can decode externally and supply PCM bytes instead. Streaming has no seeking, pause, resume, clear, or public restart operation.

The `restart` error action belongs to internal reconnect work. It is not an `AudioStream` method.

Streams use the combined master tap. OpenTUI does not expose an isolated stream tap.

## Next

- [Audio](/docs/core-concepts/audio)
- [Audio capture and recording](/docs/application-apis/audio-capture)
- [Lifecycle and cleanup](/docs/core-concepts/lifecycle)

---

### Packages Web Src Content Docs Application Apis Program Status (`packages/web/src/content/docs/application-apis/program-status.mdx`)

---
title: Program status
description: Tell the terminal whether the program is idle, working, blocked, done, or failed
skill:
  intents: [program-status, osc7501, terminal, progress, renderer]
---

# Program status

OpenTUI sends [OSC 7501 Program Status Protocol](https://www.superlogical.com/rex/docs/build/program-status) reports.
A report tells the terminal what the program is doing. The terminal decides how to show it, for example as a tab
indicator, an inbox entry, or a desktop notification.

```typescript
renderer.setProgramStatus({ state: "working", app: "deploy", message: "Deploying v2.4.1" })
renderer.setProgramStatus({
  state: "blocked",
  kind: "permission",
  app: "deploy",
  message: "Approve production deploy?",
})
renderer.setProgramStatus({ state: "done", app: "deploy", message: "Deployed to 3 regions" })
```

## States

| `state`   | Meaning                                                  | Extra fields       |
| --------- | -------------------------------------------------------- | ------------------ |
| `idle`    | At rest and waiting for the next instruction             |                    |
| `working` | Running                                                  | `progress`         |
| `blocked` | Waiting for the user                                     | `kind`, `progress` |
| `done`    | Finished, and the user has not seen the result yet       |                    |
| `error`   | Failed and stopped                                       |                    |
| `clear`   | Removes the addressed record and every record beneath it | Accepts only `id`  |

`kind` is `"permission"`, `"question"`, or `"auth"`. `progress` is an integer from 0 to 100. Omit `progress` when the
work has no percentage.

Report `idle` when the user cancels work. Report `done` or `error` before the program exits, so the terminal keeps a
record for the user to find.

## Records

Each report replaces its record completely. A field missing from a report is missing from the record afterwards, so
send `app` and `title` with every report that needs them.

| Field     | Value                                                                        |
| --------- | ---------------------------------------------------------------------------- |
| `id`      | Record path such as `build/test`. Omit it for the root record                |
| `app`     | Stable program identifier such as `cargo`. Up to 32 `[A-Za-z0-9_.+-]` bytes  |
| `title`   | Short label for the record. Up to 192 UTF-8 bytes                            |
| `message` | One line that says what the record is doing or waiting for. Up to 2048 bytes |

An `id` has up to 8 segments separated by `/`. Each segment has 1 to 32 `[A-Za-z0-9_.+-]` bytes, and the whole `id` has
at most 128 bytes. A program that runs several tasks can report one record for each task:

```typescript
renderer.setProgramStatus({ state: "working", app: "deploy" })
renderer.setProgramStatus({ state: "working", id: "us-east", title: "US East", progress: 40 })
renderer.setProgramStatus({ state: "blocked", kind: "permission", id: "eu-west", title: "EU West" })
renderer.setProgramStatus({ state: "clear", id: "us-east" })
```

`title` and `message` cannot contain control characters, including newline and tab. OpenTUI encodes them as base64.

## Results

`setProgramStatus()` returns `true` when OpenTUI queues the report. It returns `false` in these cases:

- The renderer is destroyed.
- The terminal is not active, for example before `setupTerminal()` finishes or while the renderer is suspended.
- The output queue is full.

A report that breaks a rule above throws. OpenTUI checks the whole report before it writes anything.

## Detection

Terminals ignore OSC sequences they do not implement, so `setProgramStatus()` does not wait for detection. OpenTUI
sends the `OSC 7501 ; ?` query at startup, except when `TERM` names foot, whose reduced probe omits OSC capability queries.
`renderer.capabilities?.program_status` becomes `true` when the terminal replies. A `false` value can also mean that
the reply has not arrived yet.

```typescript
renderer.on("capabilities", (capabilities) => {
  console.log("program status", capabilities.program_status)
})
```

## Lifetime

Records belong to the terminal, not to the renderer. When the process exits or the shell shows a new prompt, the
terminal drops `working` and `blocked` records. `done` and `error` records stay until the terminal decides to remove
them. OpenTUI does not clear records when the renderer is destroyed or suspended.

## Multiplexers

OpenTUI does not wrap reports in tmux or GNU Screen passthrough. A passthrough report would reach the outer terminal,
which cannot tell panes apart or see the program exit. A multiplexer that implements OSC 7501 receives reports
directly.

## Next

- [Notifications](/docs/core-concepts/notifications) covers one-time desktop notifications.
- [Terminal capabilities](/docs/reference/terminal-capabilities) explains query and detection timing.
- [Renderer](/docs/core-concepts/renderer) documents the renderer and its capability event.

---

### Packages Web Src Content Docs Bindings React (`packages/web/src/content/docs/bindings/react.mdx`)

---
title: React bindings
description: Build terminal UIs with React and OpenTUI
skill:
  entry: true
  intents: [react, jsx, hooks, keyboard, paste, focus, blur, selection, animation, testing]
---

# React bindings

Use React components and hooks to build an OpenTUI application.

## Requirements

`@opentui/react` requires React `>=19.2.0`. See [Runtime and platform support](/docs/getting-started/runtime-support)
for the current runtime, operating system, architecture, and native renderer matrix.

## Installation

Create a project with [Bun](https://bun.sh) and [create-tui](https://github.com/msmps/create-tui):

```bash
bun create tui --template react
```

Or add the packages to an existing project:

```bash
bun install @opentui/react @opentui/core react
```

## Quick start

```tsx
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`:

```json
{
  "compilerOptions": {
    "lib": ["ESNext", "DOM"],
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "jsx": "react-jsx",
    "jsxImportSource": "@opentui/react",
    "strict": true,
    "skipLibCheck": true
  }
}
```

## Runtime-loaded modules

React hosts that load TSX modules at runtime use this framework-specific import:

```ts
import "@opentui/react/runtime-plugin-support"
```

See [Load plugins and modules at runtime](/docs/extend/runtime-plugins) for Bun setup, import order, module maps, and
trust boundaries.

## Components

React JSX intrinsic elements map to Core renderables and use kebab-case names such as `<ascii-font>` and
`<tab-select>`. See the [Components overview](/docs/components) for availability and
registration requirements. React creates one Core renderable for each element and updates the renderable tree on each
commit.

## API reference

### `createRoot(renderer)`

`createRoot(renderer)` adopts an existing `CliRenderer`. It returns a React root with `render(node)` and `unmount()`.

```tsx
import { createCliRenderer } from "@opentui/core"
import { createRoot } from "@opentui/react"

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

const renderer = await createCliRenderer()
const root = createRoot(renderer)
root.render(<App />)
```

A later `root.render(node)` updates the mounted tree in place and keeps component state, as in `react-dom`. It also
retries a tree that failed with an uncaught error.

For plugin slots, see [Plugin slots](/docs/plugins/slots) and [React plugin slots](/docs/plugins/react).

## Lifecycle and cleanup

The React root does not own the renderer. `root.unmount()` removes the React tree while the renderer stays active.
Called outside React work, it runs React effect cleanup before it returns; called from an effect, it runs the cleanup
right after the current commit. `renderer.destroy()` releases the terminal and renderer resources. It also unmounts the
React root.

The code that creates the renderer owns `renderer.destroy()`. Call it on every application shutdown path. See
[Lifecycle and cleanup](/docs/core-concepts/lifecycle) for signal and failure handling.

## Hooks

The event hooks subscribe after mount and remove their listeners during React effect cleanup.

### `useRenderer()`

Access the OpenTUI renderer instance.

```tsx
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.

```tsx
import { useKeyboard, useRenderer } from "@opentui/react"

function App() {
  const renderer = useRenderer()

  useKeyboard((key) => {
    if (key.name === "escape") {
      renderer.destroy()
    }
  })

  return <text>Press ESC to close</text>
}
```

To handle release events:

```tsx
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.

```tsx
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.

```tsx
import { useTerminalDimensions } from "@opentui/react"

function App() {
  const { width, height } = useTerminalDimensions()

  return (
    <text>
      Terminal: {width}x{height}
    </text>
  )
}
```

### `usePaste(handler)`

Handle terminal paste events (bracketed paste).

```tsx
import { decodePasteBytes } from "@opentui/core"
import { usePaste } from "@opentui/react"

function App() {
  usePaste((event) => {
    const text = decodePasteBytes(event.bytes)
    console.log("Pasted text:", text)
  })

  return <text>Paste something into the terminal</text>
}
```

### `useFocus(handler)`

Subscribe to terminal window focus events.

```tsx
import { useFocus } from "@opentui/react"

function App() {
  useFocus(() => {
    console.log("Terminal gained focus")
  })

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

### `useBlur(handler)`

Subscribe to terminal window blur events.

```tsx
import { useBlur } from "@opentui/react"

function App() {
  useBlur(() => {
    console.log("Terminal lost focus")
  })

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

### `useSelectionHandler(handler)`

Handle text selection events such as mouse drag selection.

```tsx
import { useSelectionHandler } from "@opentui/react"

function App() {
  useSelectionHandler((selection) => {
    const text = selection.getSelectedText()
    console.log("Selected:", text)
  })

  return <text selectable>Select this text with your mouse</text>
}
```

### `useTimeline(options?)`

`useTimeline()` creates one `Timeline` for the mounted hook. It registers the timeline after mount and starts it unless
`autoplay` is `false`. React pauses and unregisters the same instance during effect cleanup.

```tsx
import { useTimeline } from "@opentui/react"

function App() {
  const timeline = useTimeline({ autoplay: false })
  return <text>{timeline.isPlaying ? "Playing" : "Paused"}</text>
}
```

The hook reads its options when it creates the instance. Read [Animation and Timeline](/docs/application-apis/animation)
for scheduling, callbacks, engine ownership, and cleanup.

## Styling

Style components with props or the `style` prop:

```tsx
// Direct props
<box backgroundColor="blue" padding={2}>
  <text>Hello</text>
</box>

// Style prop
<box style={{ backgroundColor: "blue", padding: 2 }}>
  <text>Hello</text>
</box>
```

## Testing

`@opentui/react/test-utils` exports `testRender(node, options)`. It is a React-aware wrapper around
[`createTestRenderer()`](/docs/core-concepts/testing). It mounts the initial node with React `act()` and returns the Core
`TestRendererSetup`. Renderer destruction unmounts the React root with `act()`.

```tsx
import { expect, test } from "bun:test"
import { testRender } from "@opentui/react/test-utils"

function App() {
  return <text>Ready</text>
}

test("renders the application", async () => {
  const setup = await testRender(<App />, { width: 20, height: 4 })

  try {
    await setup.renderOnce()
    expect(setup.captureCharFrame()).toContain("Ready")
  } finally {
    setup.renderer.destroy()
  }
})
```

The options argument is required and uses `TestRendererOptions`. The helper enables React's act-environment flag for
the mounted test root. Renderer destruction unmounts the root, calls `options.onDestroy` when supplied, and then sets
the flag to `false`. The helper does not preserve a previous global value. If `onDestroy` throws, it prevents the final
flag reset.

`testRender()` does not return the React root or a separate rerender function. Use the returned Core setup for frame
capture, waits, resize, keyboard and mouse input, native stats, and external output. Always destroy the renderer in test
teardown. Destruction unmounts the React tree and resets the global act-environment flag.

## Example: login form

OpenTUI `<input>` has no password-masking mode. This demonstration displays the password value as normal terminal text.
Do not use this pattern for real secrets.

```
/* Detailed source-code truncated for AI context efficiency. */
```

## Component extension

Register custom renderables as JSX elements:

```tsx
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 calls the constructor with `{ id, ...props }`. After construction, React applies each prop to the instance again.
For an ordinary prop, React assigns the value to the instance property of the same name. React assigns each entry of
the `style` prop as a separate property. On each update, React applies the changed props and sets removed props to
`null`. Read [Custom renderables](/docs/extend/custom-renderables) to draw custom content and to
compare React and Solid construction.

## React DevTools

Use React DevTools to inspect the React tree:

1. Install:

```bash
bun add --dev react-devtools-core@7
```

2. Start DevTools:

```bash
npx react-devtools@7
```

3. Run with the `DEV` flag:

```bash
DEV=true bun run your-app.ts
```

## Next

- [Components overview](/docs/components) lists React component availability and registration requirements.
- [Interaction, focus, and selection](/docs/core-concepts/interaction) owns shared pointer and focus behavior.
- [Animation and Timeline](/docs/application-apis/animation) documents scheduling and engine ownership.
- [React keymap integration](/docs/keymap/react) connects application commands to React state.
- [Package entry points](/docs/reference/package-entrypoints) lists the public React imports.
- [Runtime and platform support](/docs/getting-started/runtime-support) gives the tested runtime matrix.
- [Deploy an OpenTUI application](/docs/ship/deploy) helps you choose a distribution format.
- [Standalone executables](/docs/reference/standalone-executables) gives the Bun and Node.js executable procedures.
- [Troubleshooting](/docs/test-and-debug/troubleshooting) routes runtime, native loading, and terminal failures.
- [Solid bindings](/docs/bindings/solid) describes the Solid state and ownership model.

---

### Packages Web Src Content Docs Bindings Solid (`packages/web/src/content/docs/bindings/solid.mdx`)

---
title: Solid bindings
description: Build terminal UIs with Solid and OpenTUI
skill:
  entry: true
  intents: [solid, jsx, signals, hooks, keyboard, animation, testing]
---

# Solid bindings

Use Solid components and reactive primitives to build an OpenTUI application.

## Requirements

`@opentui/solid` requires `solid-js` 1.9.12 exactly. See
[Runtime and platform support](/docs/getting-started/runtime-support) for the current runtime, operating system,
architecture, and native renderer matrix.

## Installation

```bash
bun install solid-js @opentui/solid
```

## Setup

### 1. Configure TypeScript

Add JSX config to `tsconfig.json`:

```json
{
  "compilerOptions": {
    "jsx": "preserve",
    "jsxImportSource": "@opentui/solid"
  }
}
```

### 2. Configure Bun

Add preload script to `bunfig.toml`:

```toml
preload = ["@opentui/solid/preload"]
```

### 3. Configure Node compilation

The Node path requires compiled Solid TSX. Use Solid's universal transform semantics. Point the generated runtime
imports at the published `solid-js` JavaScript files:

```js
import ts from "@babel/preset-typescript"
import moduleResolver from "babel-plugin-module-resolver"
import solid from "babel-preset-solid"

export default {
  plugins: [
    [
      moduleResolver,
      {
        resolvePath(specifier) {
          if (specifier === "solid-js") return "solid-js/dist/solid.js"
          if (specifier === "solid-js/store") return "solid-js/store/dist/store.js"
          return specifier
        },
      },
    ],
  ],
  presets: [[solid, { moduleName: "@opentui/solid", generate: "universal" }], [ts]],
}
```

Use Node.js 26.4.0 or later with `--experimental-ffi`. If you enable the Node permission model, also pass `--permission`,
`--allow-ffi`, and the filesystem permissions that the application needs. The Solid Node source lane in CI compiles
TSX and runs on Linux x64.

### 4. Create the app

```tsx
import { render } from "@opentui/solid"

const App = () => <text>Hello, World!</text>

await render(App)
```

Run the app with `bun index.tsx`.

## Runtime-loaded modules

Solid hosts that load TSX modules at runtime use this framework-specific import:

```ts
import "@opentui/solid/runtime-plugin-support"
```

See [Load plugins and modules at runtime](/docs/extend/runtime-plugins) for Bun setup, import order, module maps, and
trust boundaries.

## Components

Solid JSX intrinsic elements map to Core renderables. Multi-word intrinsic names use snake_case, such as
`<ascii_font>` and `<tab_select>`. See the [Components overview](/docs/components) for availability and
registration requirements.

## API reference

### `render(node, rendererOrConfig?)`

Render a Solid component tree into a CLI renderer. Pass an existing `CliRenderer` or a `CliRendererConfig`.

```tsx
import { render } from "@opentui/solid"

const App = () => <text>Hello, Solid!</text>

// Simple usage
await render(() => <App />)

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

Parameters:

- `node`: Function that returns a JSX element.
- `rendererOrConfig`: Optional `CliRenderer` instance or `CliRendererConfig`.

### `testRender(node, options?)`

Create a test renderer for frame and interaction tests.

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

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

### `extend(components)`

Register custom renderables as JSX intrinsic elements.

```tsx
import { BoxRenderable } from "@opentui/core"
import { extend } from "@opentui/solid"

declare module "@opentui/solid" {
  interface OpenTUIComponents {
    custom_box: typeof BoxRenderable
  }
}

extend({ custom_box: BoxRenderable })
```

The `declare module` block adds the element to the JSX types.
Solid calls the constructor with `{ id }` only. After construction, Solid applies each JSX prop to the instance.
A class that reads a value only from its constructor options does not get that value from JSX.
Read [Custom renderables](/docs/extend/custom-renderables) to draw custom content and to adapt such classes.

### `getComponentCatalogue()`

Returns the current component catalogue that powers JSX tag lookup.

For plugin slots, see [Plugin slots](/docs/plugins/slots) and [Solid plugin slots](/docs/plugins/solid).

## Lifecycle and cleanup

`render()` returns a promise that resolves after the initial mount. It does not return a disposer. When you pass a
renderer, Solid adopts it but does not take application ownership. The code that created that renderer must destroy it.

Renderer destruction disposes the Solid root. Disposal runs `onCleanup` callbacks and removes subscriptions created by
the Solid hooks. If `render()` creates the renderer from a config object, a component can get it with `useRenderer()`
and call `renderer.destroy()` during application shutdown.

See [Lifecycle and cleanup](/docs/core-concepts/lifecycle) for renderer shutdown and failure handling.

## Scrollback writers

In [`split-footer`](/docs/core-concepts/renderer#choose-a-screen-mode) mode with `externalOutputMode: "capture-stdout"`, the Solid binding includes helpers that append JSX-rendered output above the footer. They wrap [`renderer.writeToScrollback`](/docs/core-concepts/renderer#choose-a-screen-mode) so you can write scrollback content with signals and components.

### `writeSolidToScrollback(renderer, node, options?)`

Render a JSX node once and append it as a scrollback commit.

```tsx
import { writeSolidToScrollback } from "@opentui/solid"

writeSolidToScrollback(renderer, () => <text fg="#8BD5CA">api responded in 12ms</text>)
```

### `createScrollbackWriter(node, options?)`

If you need to pass the same JSX rendering to multiple `writeToScrollback` calls (or hold onto the writer inside your own code), use the lower-level factory:

```tsx
import { createScrollbackWriter } from "@opentui/solid"

const writer = createScrollbackWriter(() => <text>logged at {new Date().toISOString()}</text>, { startOnNewLine: true })

renderer.writeToScrollback(writer)
```

### Options

| Option            | Type      | Default        | Description                                                              |
| ----------------- | --------- | -------------- | ------------------------------------------------------------------------ |
| `width`           | `number`  | renderer width | Override the snapshot width in columns                                   |
| `height`          | `number`  | measured auto  | Override the snapshot height in rows (otherwise measured from layout)    |
| `rowColumns`      | `number`  | snapshot width | Explicit last-row column count for tail tracking                         |
| `startOnNewLine`  | `boolean` | `true`         | Insert a newline before this commit if the previous commit ended mid-row |
| `trailingNewline` | `boolean` | `true`         | Append a newline after the final row                                     |

The writer returns a `ScrollbackSnapshot`. Its teardown disposes the inner Solid subtree after the snapshot renders.
Use the Core [`ScrollbackSurface`](/docs/core-concepts/renderer#choose-a-screen-mode) APIs for streaming commits that
render the same tree more than once.

## Hooks

The event hooks subscribe after mount and remove their listeners during Solid owner cleanup.

### `useRenderer()`

Access the OpenTUI renderer instance.

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

const App = () => {
  const renderer = useRenderer()

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

  return <box />
}
```

### `useKeyboard(handler, options?)`

Subscribe to keyboard events.

```tsx
import { useKeyboard, useRenderer } from "@opentui/solid"

const App = () => {
  const renderer = useRenderer()

  useKeyboard((key) => {
    if (key.name === "escape") {
      renderer.destroy()
    }
  })

  return <text>Press ESC to close</text>
}
```

With release events:

```tsx
import { createSignal } from "solid-js"

const App = () => {
  const [pressedKeys, setPressedKeys] = createSignal(new Set<string>())

  useKeyboard(
    (event) => {
      setPressedKeys((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(pressedKeys()).join(", ") || "none"}</text>
}
```

### `onResize(callback)`

Handle terminal resize events.

```tsx
import { onResize } from "@opentui/solid"

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

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

### `onFocus(callback)`

Run side effects when the terminal window gains focus.

```tsx
import { onFocus } from "@opentui/solid"

const App = () => {
  onFocus(() => {
    console.log("Terminal focused")
  })

  return <text>Switch away and back to trigger focus events</text>
}
```

### `onBlur(callback)`

Run side effects when the terminal window loses focus.

```tsx
import { onBlur } from "@opentui/solid"

const App = () => {
  onBlur(() => {
    console.log("Terminal blurred")
  })

  return <text>Switch away and back to trigger blur events</text>
}
```

These hooks listen for terminal focus-in/focus-out events when the terminal emulator supports them.

### `useTerminalDimensions()`

Get reactive terminal dimensions (returns a Solid signal).

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

const App = () => {
  const dimensions = useTerminalDimensions()

  return (
    <text>
      Terminal: {dimensions().width}x{dimensions().height}
    </text>
  )
}
```

### `usePaste(handler)`

Subscribe to paste events.

```tsx
import { usePaste } from "@opentui/solid"

const textDecoder = new TextDecoder()

const App = () => {
  usePaste((event) => {
    console.log("Pasted:", textDecoder.decode(event.bytes))
  })

  return <text>Paste something!</text>
}
```

### `useSelectionHandler(callback)`

Handle text selection events.

```tsx
import { useSelectionHandler } from "@opentui/solid"

const App = () => {
  useSelectionHandler((selection) => {
    console.log("Selected:", selection)
  })

  return <text selectable>Select me!</text>
}
```

### `useTimeline(options?)`

`useTimeline()` creates and returns one `Timeline` during component setup. It registers the timeline on mount and starts
it unless `autoplay` is `false`. Solid pauses and unregisters it during owner cleanup.

```tsx
import { useTimeline } from "@opentui/solid"

const App = () => {
  const timeline = useTimeline({ autoplay: false })
  return <text>{timeline.isPlaying ? "Playing" : "Paused"}</text>
}
```

The hook reads its options during component setup. Read
[Animation and Timeline](/docs/application-apis/animation) for scheduling, callbacks, engine ownership, and cleanup.

## Special components

### `Portal`

Render children into a different mount point. Use this for content such as modals and overlays.

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

const App = () => {
  const renderer = useRenderer()

  return (
    <box>
      <text>Main content</text>
      <Portal mount={renderer.root}>
        <box border>Overlay</box>
      </Portal>
    </box>
  )
}
```

### `Dynamic`

Render arbitrary intrinsic elements or components dynamically.

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

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

  return <Dynamic component={isMultiline() ? "textarea" : "input"} />
}
```

## Testing

`testRender(node, options?)` mounts a Solid root on a Core test renderer. It returns the Core `TestRendererSetup`.
Destroying the test renderer disposes the Solid root and runs `onCleanup` callbacks.

```tsx
import { expect, test } from "bun:test"
import { testRender } from "@opentui/solid"

const App = () => <text>Ready</text>

test("renders the application", async () => {
  const setup = await testRender(() => <App />, { width: 20, height: 4 })

  try {
    await setup.renderOnce()
    expect(setup.captureCharFrame()).toContain("Ready")
  } finally {
    setup.renderer.destroy()
  }
})
```

Use the returned setup for frame capture, waits, resize, keyboard and mouse input, native stats, and external output.
Always destroy the renderer during test cleanup.

## Example: counter

```tsx
import { render, useKeyboard, useRenderer } from "@opentui/solid"
import { createSignal } from "solid-js"

const App = () => {
  const [count, setCount] = createSignal(0)
  const renderer = useRenderer()

  useKeyboard((key) => {
    if (key.name === "up") setCount((c) => c + 1)
    if (key.name === "down") setCount((c) => c - 1)
    if (key.name === "escape") renderer.destroy()
  })

  return (
    <box border padding={2}>
      <text>Count: {count()}</text>
      <text fg="#888">Up/Down to change, ESC to close</text>
    </box>
  )
}

await render(App)
```

## React and Solid differences

| Aspect                            | React                                                    | Solid                                                  |
| --------------------------------- | -------------------------------------------------------- | ------------------------------------------------------ |
| Render function                   | `createRoot(renderer).render(<App />)`                   | `render(() => <App />)`                                |
| Component naming                  | kebab-case (`ascii-font`)                                | snake_case (`ascii_font`)                              |
| State                             | `useState`                                               | `createSignal`                                         |
| Effects                           | `useEffect`                                              | `onMount`, `onCleanup`                                 |
| Resize hook                       | `useOnResize(callback)`                                  | `onResize(callback)`                                   |
| Dimensions                        | Returns object: `dimensions.width`                       | Returns signal: `dimensions().width`                   |
| Focus, blur, paste, and selection | `useFocus`, `useBlur`, `usePaste`, `useSelectionHandler` | `onFocus`, `onBlur`, `usePaste`, `useSelectionHandler` |
| Special components                | None                                                     | `Portal`, `Dynamic`                                    |

## Next

- [React bindings](/docs/bindings/react) describes the React state and ownership model.
- [Components overview](/docs/components) lists Solid component availability and registration requirements.
- [Interaction, focus, and selection](/docs/core-concepts/interaction) owns shared pointer and focus behavior.
- [Animation and Timeline](/docs/application-apis/animation) documents scheduling and engine ownership.
- [Solid keymap integration](/docs/keymap/solid) connects application commands to Solid state.
- [Package entry points](/docs/reference/package-entrypoints) lists the public Solid imports.
- [Runtime and platform support](/docs/getting-started/runtime-support) gives the tested runtime matrix.
- [Deploy an OpenTUI application](/docs/ship/deploy) helps you choose a distribution format.
- [Standalone executables](/docs/reference/standalone-executables) gives the Bun and Node.js executable procedures.
- [Troubleshooting](/docs/test-and-debug/troubleshooting) routes runtime, native loading, and terminal failures.

---

### Packages Web Src Content Docs Components Ascii Font (`packages/web/src/content/docs/components/ascii-font.mdx`)

---
title: ASCIIFont
description: Display text with a bundled ASCII art font
---

# ASCIIFont

`ASCIIFont` draws short text with bundled ASCII art fonts. Use [Text](/docs/components/text) for normal labels and prose.

## Availability

| Field           | Availability               |
| --------------- | -------------------------- |
| Package         | `@opentui/core`            |
| Core renderable | `ASCIIFontRenderable`      |
| React           | `<ascii-font>` (automatic) |
| Solid           | `<ascii_font>` (automatic) |
| Status          | Built in                   |

## Basic usage

### Renderable API

```typescript
import { ASCIIFontRenderable, RGBA, createCliRenderer } from "@opentui/core"

const renderer = await createCliRenderer()

const title = new ASCIIFontRenderable(renderer, {
  id: "title",
  text: "OPENTUI",
  font: "tiny",
  color: RGBA.fromInts(255, 255, 255, 255),
})

renderer.root.add(title)
```

## Available fonts

OpenTUI includes several ASCII art font styles:

```typescript
// Small, compact font
{
  font: "tiny"
}

// Block style font
{
  font: "block"
}

// Shaded style font
{
  font: "shade"
}

// Slick style font
{
  font: "slick"
}

// Large font
{
  font: "huge"
}

// Grid style font
{
  font: "grid"
}

// Pallet style font
{
  font: "pallet"
}
```

The `tiny` font draws `OPEN` in two rows:

```text terminal=ascii-font-tiny
█▀█ █▀█ █▀▀ █▄ █
█▄█ █▀▀ ██▄ █ ▀█
```

The `block` font draws the same text in six rows:

```text terminal=ascii-font-block
 ██████╗  ██████╗  ███████╗ ███╗   ██╗
██╔═══██╗ ██╔══██╗ ██╔════╝ ████╗  ██║
██║   ██║ ██████╔╝ █████╗   ██╔██╗ ██║
██║   ██║ ██╔═══╝  ██╔══╝   ██║╚██╗██║
╚██████╔╝ ██║      ███████╗ ██║ ╚████║
 ╚═════╝  ╚═╝      ╚══════╝ ╚═╝  ╚═══╝
```

## Positioning

ASCIIFont inherits the standard layout positioning options. To position it at coordinates relative to its parent, use absolute positioning with `left` and `top`:

```typescript
const title = new ASCIIFontRenderable(renderer, {
  id: "title",
  text: "TITLE",
  font: "block",
  color: RGBA.fromHex("#FFFF00"),
  position: "absolute",
  left: 10,
  top: 2,
})
```

## Properties

| Property          | Type                                   | Default         | Description                |
| ----------------- | -------------------------------------- | --------------- | -------------------------- |
| `text`            | `string`                               | `""`            | Text to display            |
| `font`            | `ASCIIFontName`                        | `"tiny"`        | Font style                 |
| `color`           | `ColorInput \| ColorInput[]`           | `"#FFFFFF"`     | Text color or color bands  |
| `backgroundColor` | `ColorInput`                           | `"transparent"` | Background color           |
| `selectable`      | `boolean`                              | `true`          | Whether text is selectable |
| `selectionBg`     | `ColorInput`                           | -               | Selection background color |
| `selectionFg`     | `ColorInput`                           | -               | Selection foreground color |
| `position`        | `"relative" \| "absolute"`             | `"relative"`    | Effective positioning mode |
| `top`             | number, `"auto"`, or percentage string | -               | Top position offset        |
| `right`           | number, `"auto"`, or percentage string | -               | Right position offset      |
| `bottom`          | number, `"auto"`, or percentage string | -               | Bottom position offset     |
| `left`            | number, `"auto"`, or percentage string | -               | Left position offset       |

## Example: welcome screen

```typescript
import { ASCIIFontRenderable, BoxRenderable, TextRenderable, createCliRenderer } from "@opentui/core"

const renderer = await createCliRenderer()

const welcomeScreen = new BoxRenderable(renderer, {
  width: "100%",
  height: "100%",
  flexDirection: "column",
  alignItems: "center",
  justifyContent: "center",
})
welcomeScreen.add(
  new ASCIIFontRenderable(renderer, {
    text: "OPENTUI",
    font: "huge",
    color: "#00FFFF",
  }),
)
welcomeScreen.add(
  new TextRenderable(renderer, {
    content: "Terminal UI Framework",
    fg: "#888888",
  }),
)
welcomeScreen.add(
  new TextRenderable(renderer, {
    content: "Press any key to continue...",
    fg: "#444444",
  }),
)

renderer.root.add(welcomeScreen)
```

## Dynamic text

Update the text content dynamically:

```typescript
const counter = new ASCIIFontRenderable(renderer, {
  id: "counter",
  text: "0",
  font: "block",
  color: RGBA.fromHex("#FF0000"),
})

let count = 0
setInterval(() => {
  count++
  counter.text = count.toString()
}, 1000)
```

## Color effects

Create a shadow by overlaying two ASCII fonts:

```typescript
import { ASCIIFontRenderable, BoxRenderable, createCliRenderer } from "@opentui/core"

const renderer = await createCliRenderer()
const shadowTitle = new BoxRenderable(renderer, { position: "relative" })
shadowTitle.add(
  new ASCIIFontRenderable(renderer, {
    text: "HELLO",
    font: "block",
    color: "#880000",
    position: "absolute",
    left: 1,
    top: 1,
    zIndex: 0,
  }),
)
shadowTitle.add(
  new ASCIIFontRenderable(renderer, {
    text: "HELLO",
    font: "block",
    color: "#FF0000",
    zIndex: 1,
  }),
)
renderer.root.add(shadowTitle)
```

## Related concepts

See [Layout](/docs/core-concepts/layout) for positioning and [Colors](/docs/core-concepts/colors) for accepted color values. Use [FrameBuffer](/docs/components/frame-buffer) for graphics that need direct cell drawing.

---

### Packages Web Src Content Docs Components Box (`packages/web/src/content/docs/components/box.mdx`)

---
title: Box
description: Group and lay out child content with borders and backgrounds
---

# Box

`Box` lays out child renderables and can draw a background, border, and title. Use [ScrollBox](/docs/components/scrollbox) when the children must scroll.

## Availability

| Field           | Availability        |
| --------------- | ------------------- |
| Package         | `@opentui/core`     |
| Core renderable | `BoxRenderable`     |
| React           | `<box>` (automatic) |
| Solid           | `<box>` (automatic) |
| Status          | Built in            |

## Basic usage

### Renderable API

```typescript
import { BoxRenderable, TextRenderable, createCliRenderer } from "@opentui/core"

const renderer = await createCliRenderer()

const panel = new BoxRenderable(renderer, {
  id: "panel",
  width: 30,
  height: 10,
  backgroundColor: "#333366",
  borderStyle: "double",
  borderColor: "#FFFFFF",
})

renderer.root.add(panel)
```

## Border styles

```typescript
// No border
{
  border: false
}

// Simple border (default style)
{
  border: true
}

// Specific border styles
{
  borderStyle: "single"
} // Single line: ┌─┐│└─┘
{
  borderStyle: "double"
} // Double line: ╔═╗║╚═╝
{
  borderStyle: "rounded"
} // Rounded corners: ╭─╮│╰─╯
{
  borderStyle: "heavy"
} // Heavy lines: ┏━┓┃┗━┛
```

```text terminal=box-borders
┌─────single─────┐  ╔═════double═════╗
│ single line    │  ║ double lines   ║
└────────────────┘  ╚════════════════╝

╭────rounded─────╮  ┏━━━━━heavy━━━━━━┓
│ round corners  │  ┃ heavy strokes  ┃
╰────────────────╯  ┗━━━━━━━━━━━━━━━━┛
```

## Titles

Add a title and bottom title to the box border:

```typescript
const panel = new BoxRenderable(renderer, {
  id: "settings",
  width: 40,
  height: 15,
  borderStyle: "rounded",
  title: "Settings",
  titleColor: "yellow",
  titleAlignment: "center",
  bottomTitle: "Footer",
  bottomTitleAlignment: "center",
})
```

### Title alignment (both top and bottom titles)

```typescript
{
  titleAlignment: "left"
} // ┌─ Title ────────┐
{
  titleAlignment: "center"
} // ┌──── Title ─────┐
{
  titleAlignment: "right"
} // ┌────────── Title ┐

{
  bottomTitleAlignment: "left"
} // └─ Title ────────┘
{
  bottomTitleAlignment: "center"
} // └──── Title ─────┘
{
  bottomTitleAlignment: "right"
} // └────────── Title ┘
```

```text terminal=box-title-alignment
┌───────────settings───────────┐
│ Top and bottom titles        │
└────────────────────────close─┘
```

## Layout container

Box works as a flex container for child elements:

```typescript
const container = new BoxRenderable(renderer, {
  flexDirection: "column",
  justifyContent: "space-between",
  alignItems: "stretch",
  width: 50,
  height: 20,
  padding: 1,
  gap: 1,
})
const content = new BoxRenderable(renderer, { flexGrow: 1, backgroundColor: "#222" })

content.add(new TextRenderable(renderer, { content: "Content area" }))
container.add(new TextRenderable(renderer, { content: "Header" }))
container.add(content)
container.add(new TextRenderable(renderer, { content: "Footer" }))
```

## Mouse events

Handle mouse interactions on the box:

```typescript
const button = new BoxRenderable(renderer, {
  id: "button",
  width: 12,
  height: 3,
  border: true,
  backgroundColor: "#444",
  onMouseDown: () => {
    console.log("Button clicked!")
  },
  onMouseOver: () => {
    button.backgroundColor = "#666"
  },
  onMouseOut: () => {
    button.backgroundColor = "#444"
  },
})
```

## Properties

| Property               | Type               | Default        | Description                             |
| ---------------------- | ------------------ | -------------- | --------------------------------------- |
| `width`                | `number \| string` | -              | Width in terminal columns or percentage |
| `height`               | `number \| string` | -              | Height in rows or percentage            |
| `backgroundColor`      | `string \| RGBA`   | `transparent`  | Background fill color                   |
| `border`               | `boolean`          | `false`        | Show border                             |
| `borderStyle`          | `string`           | `"single"`     | Border style                            |
| `borderColor`          | `string \| RGBA`   | `#FFFFFF`      | Border color                            |
| `title`                | `string`           | -              | Title text in border                    |
| `titleColor`           | `string \| RGBA`   | `borderColor`  | Color of the title text                 |
| `titleAlignment`       | `string`           | `"left"`       | Title position                          |
| `bottomTitle`          | `string`           | -              | Bottom title text in border             |
| `bottomTitleAlignment` | `string`           | `"left"`       | Bottom title position                   |
| `padding`              | `number`           | `0`            | Internal padding                        |
| `gap`                  | `number \| string` | -              | Gap between children                    |
| `flexDirection`        | `string`           | `"column"`     | Child layout direction                  |
| `justifyContent`       | `string`           | `"flex-start"` | Main axis alignment                     |
| `alignItems`           | `string`           | `"stretch"`    | Cross axis alignment                    |

## Example: card component

```typescript
import { BoxRenderable, TextRenderable, t, bold, fg } from "@opentui/core"

function Card(props: { title: string; description: string }) {
  const card = new BoxRenderable(renderer, {
    width: 40,
    borderStyle: "rounded",
    borderColor: "#666",
    padding: 1,
    margin: 1,
  })
  card.add(
    new TextRenderable(renderer, {
      content: t`${bold(fg("#00FFFF")(props.title))}`,
    }),
  )
  card.add(
    new TextRenderable(renderer, {
      content: props.description,
      fg: "#AAAAAA",
    }),
  )
  return card
}

const cards = new BoxRenderable(renderer, { flexDirection: "row", flexWrap: "wrap" })
cards.add(Card({ title: "Feature 1", description: "Description of feature 1" }))
cards.add(Card({ title: "Feature 2", description: "Description of feature 2" }))
cards.add(Card({ title: "Feature 3", description: "Description of feature 3" }))
renderer.root.add(cards)
```

## Related concepts

Read [Layout](/docs/core-concepts/layout) for sizing and flex behavior. See [Colors](/docs/core-concepts/colors) for color formats and [Interaction, focus, and selection](/docs/core-concepts/interaction) for mouse event propagation. Use [Text](/docs/components/text) for labels and other text content.

---

### Packages Web Src Content Docs Components Code (`packages/web/src/content/docs/components/code.mdx`)

---
title: Code
description: Display syntax-highlighted code with Tree-sitter
---

# Code

`Code` displays source text with Tree-sitter syntax highlighting. Use [Markdown](/docs/components/markdown) for documents or [Diff](/docs/components/diff) for changed lines.

## Availability

| Field           | Availability         |
| --------------- | -------------------- |
| Package         | `@opentui/core`      |
| Core renderable | `CodeRenderable`     |
| React           | `<code>` (automatic) |
| Solid           | `<code>` (automatic) |
| Status          | Built in             |

## Basic usage

### Renderable API

```typescript
import { CodeRenderable, createCliRenderer, SyntaxStyle, RGBA } from "@opentui/core"

const renderer = await createCliRenderer()

const syntaxStyle = SyntaxStyle.fromStyles(
  {
    default: { fg: RGBA.defaultForeground() },
    keyword: { fg: RGBA.defaultForeground(), bold: true },
    string: { fg: RGBA.fromIndex(247) },
    comment: { fg: RGBA.fromIndex(244), italic: true },
  },
  renderer.nativeScene,
)

const code = new CodeRenderable(renderer, {
  id: "code",
  content: `function hello() {
  // This is a comment
  const message = "Hello, world!"
  return message
}`,
  filetype: "javascript",
  syntaxStyle,
  width: 50,
  height: 10,
})

renderer.root.add(code)
```

```text terminal=code-highlighted
function hello() {
  // This is a comment
  const message = "Hello, world!"
  return message
}
```

## Creating syntax styles

`SyntaxStyle.fromStyles(definitions, owner)` maps syntax token names to colors and attributes.
It creates a native style, so it needs an owner. Pass `renderer.nativeScene`.
Without a renderer, pass a [`ResourceContext`](/docs/reference/buffer-api#create-and-destroy-a-buffer).

`fromStyles()` and `registerStyle()` copy each definition, including its colors.
`getStyle()`, `getAllStyles()`, and `mergeStyles()` return copies.
Changes to an input object or a returned value do not change the registered style.
To change a style, call `registerStyle()` again.

```typescript
import { SyntaxStyle, RGBA, parseColor } from "@opentui/core"

const syntaxStyle = SyntaxStyle.fromStyles(
  {
    // Basic tokens
    keyword: { fg: RGBA.fromHex("#FF7B72"), bold: true },
    "keyword.import": { fg: RGBA.fromHex("#FF7B72"), bold: true },
    "keyword.operator": { fg: RGBA.fromHex("#FF7B72") },

    string: { fg: RGBA.fromHex("#A5D6FF") },
    comment: { fg: RGBA.fromHex("#8B949E"), italic: true },
    number: { fg: RGBA.fromHex("#79C0FF") },
    boolean: { fg: RGBA.fromHex("#79C0FF") },
    constant: { fg: RGBA.fromHex("#79C0FF") },

    // Functions and types
    function: { fg: RGBA.fromHex("#D2A8FF") },
    "function.call": { fg: RGBA.fromHex("#D2A8FF") },
    "function.method.call": { fg: RGBA.fromHex("#D2A8FF") },
    type: { fg: RGBA.fromHex("#FFA657") },
    constructor: { fg: RGBA.fromHex("#FFA657") },

    // Variables and properties
    variable: { fg: RGBA.fromHex("#E6EDF3") },
    "variable.member": { fg: RGBA.fromHex("#79C0FF") },
    property: { fg: RGBA.fromHex("#79C0FF") },

    // Operators and punctuation
    operator: { fg: RGBA.fromHex("#FF7B72") },
    punctuation: { fg: RGBA.fromHex("#F0F6FC") },
    "punctuation.bracket": { fg: RGBA.fromHex("#F0F6FC") },
    "punctuation.delimiter": { fg: RGBA.fromHex("#C9D1D9") },

    // Default fallback
    default: { fg: RGBA.fromHex("#E6EDF3") },
  },
  renderer.nativeScene,
)
```

A style belongs to one owner. To use the same definitions in another renderer, create a new style for that renderer.
Destroy the new style when that renderer no longer needs it:

```typescript
const definitions = syntaxStyle.getAllStyles()
const boundStyle = SyntaxStyle.fromStyles(definitions, destination.nativeScene)
// Use boundStyle in the destination's Code or Markdown nodes.
// After you destroy those nodes:
boundStyle.destroy()
```

`CodeRenderable` does not destroy the `syntaxStyle` you pass. You destroy it.
Text buffers and edit buffers accept only a style with the same owner.
A style from another owner throws an owner-mismatch error. Core does not copy it.

`getAllStyles()` returns only registered definitions. It does not return the styles that native code creates for text chunks.
Pass its `Map` directly to `fromStyles()` to keep the registration order, including numeric names.
An object lists numeric names first, which can change style IDs.

### Style properties

Each style definition can include:

| Property    | Type      | Description             |
| ----------- | --------- | ----------------------- |
| `fg`        | `RGBA`    | Foreground (text) color |
| `bg`        | `RGBA`    | Background color        |
| `bold`      | `boolean` | Bold text               |
| `italic`    | `boolean` | Italic text             |
| `underline` | `boolean` | Underlined text         |
| `dim`       | `boolean` | Dimmed text             |

## Supported languages

OpenTUI bundles Tree-sitter parsers for these languages:

- JavaScript and JSX
- TypeScript and TSX
- Markdown and Markdown inline
- Zig

Other grammars require configuration. See the [Tree-sitter reference](/docs/reference/tree-sitter).

## Streaming mode

Enable streaming mode when content arrives incrementally, like LLM output:

```typescript
const code = new CodeRenderable(renderer, {
  id: "streaming-code",
  content: "",
  filetype: "typescript",
  syntaxStyle,
  streaming: true, // Enable streaming mode
})

// Later, append content
code.content += "const x = 1\n"
code.content += "const y = 2\n"
```

With `drawUnstyledText: false`, streaming mode changes what `Code` shows while a highlight runs:

- Without streaming, `Code` shows nothing until each highlight completes.
- With streaming, `Code` shows nothing until the first highlight completes. Later updates keep the previous highlighted text visible until the new highlight completes.

With `drawUnstyledText: true`, a `content` change shows the new text unstyled while its highlight runs, with or without streaming.
In streaming mode, after the first highlight, other changes that start a highlight, such as `filetype` or `conceal`, keep the previous highlighted text visible.
Each highlight processes the complete current `content`.

## Text selection

Text selection is on by default. Set `selectable: false` to turn it off. Use `selectionBg` and `selectionFg` to set selection colors:

```typescript
const code = new CodeRenderable(renderer, {
  id: "code",
  content: sourceCode,
  filetype: "typescript",
  syntaxStyle,
  selectable: true,
  selectionBg: "#264F78",
  selectionFg: "#FFFFFF",
})
```

## Concealment

Set `conceal` to hide concealed syntax elements, such as Markdown formatting characters:

```typescript
const code = new CodeRenderable(renderer, {
  id: "markdown",
  content: "# Heading\n**bold** text",
  filetype: "markdown",
  syntaxStyle,
  conceal: true, // Hide formatting characters
})
```

## With line numbers

Use `LineNumberRenderable` to add line numbers:

```typescript
import { CodeRenderable, LineNumberRenderable, RGBA, ScrollBoxRenderable, SyntaxStyle } from "@opentui/core"

const sourceCode = "const answer = 42\nconsole.log(answer)\n"
const syntaxStyle = SyntaxStyle.fromStyles(
  {
    default: { fg: RGBA.fromHex("#E6EDF3") },
  },
  renderer.nativeScene,
)

const code = new CodeRenderable(renderer, {
  id: "code",
  content: sourceCode,
  filetype: "typescript",
  syntaxStyle,
  width: "100%",
})

const lineNumbers = new LineNumberRenderable(renderer, {
  id: "code-with-lines",
  target: code,
  minWidth: 3,
  paddingRight: 1,
  fg: "#6b7280",
  bg: "#161b22",
  width: "100%",
})

// Wrap in ScrollBox for scrolling
const scrollbox = new ScrollBoxRenderable(renderer, {
  id: "scrollbox",
  width: 60,
  height: 20,
})
scrollbox.add(lineNumbers)
renderer.root.add(scrollbox)
```

## Properties

| Property           | Type               | Default  | Description                                                                       |
| ------------------ | ------------------ | -------- | --------------------------------------------------------------------------------- |
| `content`          | `string`           | `""`     | Source code to display                                                            |
| `filetype`         | `string`           | -        | Language for syntax highlighting                                                  |
| `syntaxStyle`      | `SyntaxStyle`      | required | Syntax style for tokens                                                           |
| `streaming`        | `boolean`          | `false`  | Change what shows while a highlight runs (see [Streaming mode](#streaming-mode))  |
| `conceal`          | `boolean`          | `true`   | Hide concealed syntax elements                                                    |
| `drawUnstyledText` | `boolean`          | `true`   | Show unstyled text while a highlight runs (see [Streaming mode](#streaming-mode)) |
| `treeSitterClient` | `TreeSitterClient` | -        | Custom Tree-sitter client instance                                                |

When the renderable has a `filetype`, a change to `content`, `filetype`, `syntaxStyle`, `conceal`, `drawUnstyledText`, `streaming`, or `treeSitterClient` starts a new highlight.
`Code` starts each highlight in its [update hook](/docs/extend/custom-renderables#schedule-updates) (`onUpdate`) during the next frame.
`Code` does not start a highlight while it or an ancestor has `visible: false`.

### Inherited from TextBufferRenderable

| Property       | Type                            | Default     | Description                                 |
| -------------- | ------------------------------- | ----------- | ------------------------------------------- |
| `fg`           | `string \| RGBA`                | `#FFFFFF`   | Default foreground color                    |
| `bg`           | `string \| RGBA`                | transparent | Background color                            |
| `selectable`   | `boolean`                       | `true`      | Whether text selection is on                |
| `selectionBg`  | `string \| RGBA`                | -           | Selection background color                  |
| `selectionFg`  | `string \| RGBA`                | -           | Selection foreground color                  |
| `wrapMode`     | `string`                        | `"word"`    | Text wrapping: `"none"`, `"char"`, `"word"` |
| `textAlign`    | `"left" \| "center" \| "right"` | `"left"`    | Per-line alignment within the text width    |
| `tabIndicator` | `string \| number`              | -           | Tab display character or width              |

## Additional properties

| Property         | Type      | Description                                                                                                    |
| ---------------- | --------- | -------------------------------------------------------------------------------------------------------------- |
| `lineCount`      | `number`  | Number of lines in the current text buffer                                                                     |
| `scrollY`        | `number`  | Current vertical scroll position (get/set)                                                                     |
| `scrollX`        | `number`  | Current horizontal scroll position (get/set)                                                                   |
| `scrollWidth`    | `number`  | Total scrollable width (read-only)                                                                             |
| `scrollHeight`   | `number`  | Total scrollable height (read-only)                                                                            |
| `isHighlighting` | `boolean` | Whether highlighting is in progress                                                                            |
| `plainText`      | `string`  | Plain text in the current text buffer. It can differ from `content` after concealment or `onChunks` transforms |

## Markdown styles

For markdown highlighting, use markup-prefixed style names:

```typescript
const markdownStyle = SyntaxStyle.fromStyles(
  {
    "markup.heading": { fg: RGBA.fromHex("#58A6FF"), bold: true },
    "markup.heading.1": { fg: RGBA.fromHex("#00FF88"), bold: true, underline: true },
    "markup.heading.2": { fg: RGBA.fromHex("#00D7FF"), bold: true },
    "markup.bold": { fg: RGBA.fromHex("#F0F6FC"), bold: true },
    "markup.strong": { fg: RGBA.fromHex("#F0F6FC"), bold: true },
    "markup.italic": { fg: RGBA.fromHex("#F0F6FC"), italic: true },
    "markup.list": { fg: RGBA.fromHex("#FF7B72") },
    "markup.quote": { fg: RGBA.fromHex("#8B949E"), italic: true },
    "markup.raw": { fg: RGBA.fromHex("#A5D6FF") },
    "markup.raw.block": { fg: RGBA.fromHex("#A5D6FF") },
    "markup.link": { fg: RGBA.fromHex("#58A6FF"), underline: true },
    "markup.link.url": { fg: RGBA.fromHex("#58A6FF"), underline: true },
    default: { fg: RGBA.fromHex("#E6EDF3") },
  },
  renderer.nativeScene,
)
```

## Related components

Use [Markdown](/docs/components/markdown) for document structure and fenced code. Add a [Line number gutter](/docs/components/line-number) to a code renderable. Use [Diff](/docs/components/diff) for unified or split patches. Use [TextTable](/docs/components/text-table) for tabular content without syntax parsing.

Read [Text and terminal cells](/docs/core-concepts/text-and-cells) for wrapping, graphemes, and display-cell widths.

---

### Packages Web Src Content Docs Components Diff (`packages/web/src/content/docs/components/diff.mdx`)

---
title: Diff
description: Display unified or split diffs with optional syntax highlighting
---

# Diff

`Diff` renders one file patch in a unified or split view with syntax highlighting and optional line numbers. Use [Code](/docs/components/code) when you do not need patch parsing.

## Availability

| Field           | Availability                                                          |
| --------------- | --------------------------------------------------------------------- |
| Package         | `@opentui/core`                                                       |
| Core renderable | `DiffRenderable`                                                      |
| React           | `<diff>` (automatic)                                                  |
| Solid           | `<diff>` is runtime built in. Its exact props declaration is missing. |
| Status          | Built in with a Solid typing limitation                               |

## Basic usage

### Renderable API

```typescript
import { DiffRenderable, SyntaxStyle, RGBA, createCliRenderer } from "@opentui/core"

const renderer = await createCliRenderer()

const syntaxStyle = SyntaxStyle.fromStyles(
  {
    default: { fg: RGBA.fromHex("#E6EDF3") },
    string: { fg: RGBA.fromHex("#A5D6FF") },
    keyword: { fg: RGBA.fromHex("#FF7B72"), bold: true },
  },
  renderer.nativeScene,
)

const patch = `diff --git a/app.ts b/app.ts
index 1111111..2222222 100644
--- a/app.ts
+++ b/app.ts
@@ -1,3 +1,3 @@
 setup()
-const a = 1
+const a = 2
 ready(a)
`

const diff = new DiffRenderable(renderer, {
  id: "diff",
  width: "100%",
  height: 16,
  diff: patch,
  view: "split",
  filetype: "typescript",
  syntaxStyle,
  showLineNumbers: true,
})

renderer.root.add(diff)
```

Monochrome views of the same patch:

Unified view:

```text terminal=diff-unified
 1   setup()
 2 - const a = 1
 2 + const a = 2
 3   ready(a)
```

Split view:

```text terminal=diff-split
 1   setup()      1   setup()
 2 - const a = 1  2 + const a = 2
 3   ready(a)     3   ready(a)
```

For multi-file input, Diff shows only the first file patch (`patches[0]`). Create one `DiffRenderable` for each file patch that you need to show.

## Split view scroll sync

In split view, set `syncScroll: true` to link the two panes. A mouse-wheel scroll in one pane then copies its `scrollX` and `scrollY` to the other pane:

```typescript
const diff = new DiffRenderable(renderer, {
  id: "diff",
  view: "split",
  syncScroll: true,
  diff: patch,
  syntaxStyle,
})

// Toggle at runtime
diff.syncScroll = false
```

Scroll sync has no effect in the unified view.

If you omit `syntaxStyle`, Diff creates an empty fallback style and destroys it when you destroy the Diff.
Destroying a Diff also destroys its inactive panes.
Diff does not destroy a `syntaxStyle` you pass. Destroy it after no renderable uses it.

## Properties

| Property              | Type                            | Default     | Description                                       |
| --------------------- | ------------------------------- | ----------- | ------------------------------------------------- |
| `diff`                | `string`                        | `""`        | Unified diff string                               |
| `view`                | `"unified"` or `"split"`        | `"unified"` | Layout style                                      |
| `syncScroll`          | `boolean`                       | `false`     | Link horizontal and vertical scroll in split view |
| `filetype`            | `string`                        | -           | Syntax highlighting language                      |
| `fg`                  | `string` or `RGBA`              | -           | Base foreground for inner code panes              |
| `syntaxStyle`         | `SyntaxStyle`                   | -           | Syntax style for code                             |
| `wrapMode`            | `"word"`, `"char"`, or `"none"` | -           | Code wrapping mode                                |
| `conceal`             | `boolean`                       | `false`     | Conceal markup when highlighting                  |
| `selectionBg`         | `string` or `RGBA`              | -           | Selection background in code panes                |
| `selectionFg`         | `string` or `RGBA`              | -           | Selection foreground in code panes                |
| `treeSitterClient`    | `TreeSitterClient`              | -           | Custom Tree-sitter client                         |
| `showLineNumbers`     | `boolean`                       | `true`      | Show line numbers                                 |
| `lineNumberFg`        | `string` or `RGBA`              | `#888888`   | Line number text color                            |
| `lineNumberBg`        | `string` or `RGBA`              | transparent | Line number background                            |
| `addedLineNumberBg`   | `string` or `RGBA`              | transparent | Line number background for added lines            |
| `removedLineNumberBg` | `string` or `RGBA`              | transparent | Line number background for removed lines          |
| `addedBg`             | `string` or `RGBA`              | `#1a4d1a`   | Background for added lines                        |
| `removedBg`           | `string` or `RGBA`              | `#4d1a1a`   | Background for removed lines                      |
| `contextBg`           | `string` or `RGBA`              | transparent | Background for context lines                      |
| `addedContentBg`      | `string` or `RGBA`              | -           | Optional content background (added)               |
| `removedContentBg`    | `string` or `RGBA`              | -           | Optional content background (removed)             |
| `contextContentBg`    | `string` or `RGBA`              | -           | Optional content background (context)             |
| `addedSignColor`      | `string` or `RGBA`              | `#22c55e`   | Sign color for added lines                        |
| `removedSignColor`    | `string` or `RGBA`              | `#ef4444`   | Sign color for removed lines                      |

Other grammars require Tree-sitter configuration. See the [Tree-sitter reference](/docs/reference/tree-sitter).

## Related components

Use [Code](/docs/components/code) for source text without patch structure. Use [Line number gutter](/docs/components/line-number) with other line-aware renderables. Use [Markdown](/docs/components/markdown) for documents and [TextTable](/docs/components/text-table) for general tabular comparisons.

---

### Packages Web Src Content Docs Components Embedded Terminal (`packages/web/src/content/docs/components/embedded-terminal.mdx`)

---
title: Embedded terminal
description: Parse VT output and draw a child terminal in the render tree
skill:
  entry: true
  intents: [embedded-terminal, terminal-renderable, ghostty, vt, pty]
---

# Embedded terminal

`EmbeddedTerminalRenderable` parses VT output and draws a terminal screen in the render tree. The parser is Ghostty's VT library, linked into the native artifact. You do not need an extra install.

The renderable is not a process and not a PTY. You write child output into it. You send encoded input back to the child.

## Availability

| Field           | Availability                 |
| --------------- | ---------------------------- |
| Package         | `@opentui/core`              |
| Core renderable | `EmbeddedTerminalRenderable` |
| React           | Unavailable                  |
| Solid           | Unavailable                  |
| Status          | Built-in Core renderable     |

The native artifact includes Ghostty VT on `x86_64` and `aarch64` for macOS, Linux glibc, Linux musl, and Windows GNU. On other targets, the constructor throws a `NativeError` with the message `ot_embedded_terminal_create failed: UnsupportedResource`.

## I/O model

The renderable owns a VT parser, a screen, scrollback, and input encoders. It does not own the child.

```text
                ╔══ child ═════════════════════════════╗
                ║                                      ║
                ║  stdout                              ║
                ║                                      ║
┌─ "input" ────▶║  stdin                               ║
├─ "response" ─▶║                                      ║
│               ╚══════════════════╤═══════════════════╝
│                                  │                    
│                                  │ write()            
│                                  ▼                    
│               ╔══ EmbeddedTerminalRenderable ════════╗
│               ║                                      ║
│               ║  ┏━━ VT parser ━━━━━━━━━━━━━━━━━━━┓  ║
│               ║  ┗━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┛  ║
│               ║                   ▼                  ║
│               ║  ┏━━ screen ━━━━━━━━━━━━━━━━━━━━━━┓  ║
│               ║  ┃  cells, cursor, scrollback     ┃  ║
│               ║  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛  ║
│               ║                                      ║
├───────────────╢  encode                              ║
└───────────────╢  drain                               ║
                ║                                      ║
                ╚══════════════════════════════════════╝
```

Write both `onData` sources to the child's stdin. Use `source` only when you log or filter. A query such as DSR produces `"response"`. A key or mouse event produces `"input"`.

## Draw VT output

```typescript
import { EmbeddedTerminalRenderable, createCliRenderer } from "@opentui/core"

const renderer = await createCliRenderer()
const terminal = new EmbeddedTerminalRenderable(renderer, {
  id: "session",
  width: 80,
  height: 24,
})

terminal.write("hello \x1b[1;32mworld\x1b[0m\r\n")
renderer.root.add(terminal)
terminal.focus()
```

`write()` accepts a string or `Uint8Array`. The parser keeps incomplete escape sequences across calls. After `write()` or a resize, the renderable drains generated replies. Nonempty reply bytes go to `onData` with source `"response"`.

Parsed child output can produce this screen:

```text terminal=embedded-terminal-vt
$ bun test
✓ parser accepts UTF-8
✓ renderer draws wide cells
2 passed, 0 failed
```

`screen()` reads the private cell buffer from the last paint. Call it after a frame renders. It trims the end of each line and drops trailing empty rows. `columns` and `rows` are the renderable's current layout size. Cursor state also comes from the last compose, not from `write()` alone.

## Attach a process

Give every child output chunk to `write()`. Give every `onData` payload to the child. Resize the child from `onTerminalResize`.

```typescript
import { EmbeddedTerminalRenderable } from "@opentui/core"

function attachChild(
  terminal: EmbeddedTerminalRenderable,
  child: {
    write(data: Uint8Array): void
    resize(cols: number, rows: number): void
  },
): void {
  terminal.onData = (data) => {
    child.write(data)
  }
  terminal.onTerminalResize = (cols, rows) => {
    child.resize(cols, rows)
  }
}
```

If `width` or `height` is a percentage or flex size, the native grid starts at `cols` and `rows`. Those values default to `80` and `24` when `width` or `height` is not a number. Create the child at that starting grid.

A computed size change resizes the emulator only while the renderable is visible. That path also drains replies and calls `onTerminalResize`. A numeric `width` or `height` that already matches the first layout does not fire the callback.

Set `TERM` and `COLORTERM` on the child when you want a typical xterm-compatible session. The examples app includes an embedded shell demo that uses `Bun.spawn` with `terminal`.

Destroy the renderable and close the child together. `destroy()` releases the native emulator. A second call is safe.

## Size and scrollback

| Option          | Default                          | Meaning                                                                                           |
| --------------- | -------------------------------- | ------------------------------------------------------------------------------------------------- |
| `cols`          | numeric `width`, otherwise `80`  | Initial emulator columns. Must be an integer from 1 through 65535.                                |
| `rows`          | numeric `height`, otherwise `24` | Initial emulator rows. Must be an integer from 1 through 65535.                                   |
| `width`         | `cols`                           | Layout width. A later size change resizes the emulator.                                           |
| `height`        | `rows`                           | Layout height. A later size change resizes the emulator.                                          |
| `maxScrollback` | `10000`                          | Scrollback budget in bytes, not lines. `0` stores no history. The value must fit in a native u32. |

`maxScrollback` is constructor-only. A visible size change floors the computed width and height and caps the native grid at 65535. It then forces a full redraw on the next compose, drains replies, and calls `onTerminalResize`. The cap does not apply to layout size or `screen()` dimensions. The renderable ignores non-finite and non-positive sizes. Hidden size changes update layout only.

## Focus and keyboard

The renderable is focusable. A left-button press inside it calls `focus()`. While the renderable has focus, the renderer routes keys and paste events to it.

`handleKeyPress()` encodes the key with the child's current keyboard mode. It sends nonempty bytes as `"input"` and returns `true` when it produces bytes. The renderable forwards Kitty releases only while it has focus. `meta` encodes as Alt, and only `super` encodes as Super.

`handlePaste()` encodes the paste with the current paste mode. Bracketed paste wraps the payload when the child enabled it. Encoding replaces certain control bytes with spaces. Without bracketed paste, newlines become carriage returns. `encodePaste()` accepts at most 1 MiB. A larger paste throws, and `handlePaste()` sends nothing to the child.

If the child enabled focus events, `focus()` and `blur()` send the matching sequences as `"input"`. If `onData` throws during `focus()`, the renderable leaves focus. `blur()` still finishes cleanup if `onData` throws.

Renderer `keyInput` listeners run before the focused renderable. Use that path for a host shortcut such as Escape. Call `stopPropagation()` or `preventDefault()` when the host must keep the key.

## Mouse and local scroll

When the child enables a cell-based mouse protocol, the renderable encodes pointer events, sends nonempty bytes as `"input"`, and stops those events. Duplicate motion and mode 1016 pixel mouse encode to empty bytes and do not stop the event. The renderable has cell coordinates only.

When the child does not encode a wheel event, the renderable scrolls its own viewport by 3 rows and stops the event. Your `onMouseDown` and related handlers still run after this forwarding.

## Selection

`selectable` defaults to `true`. The renderable joins the renderer selection and clamps drag endpoints to the visible grid. Selected cells invert on paint. `getSelectedText()` returns the emulator selection, and the renderer selection uses that same text.

The renderer starts host selection before it dispatches the mouse event. Encoded mouse bytes still go to the child. `preventDefault()` does not cancel that selection. The same gesture can update host selection and send mouse input.

## Cursor and paint

The renderable is always buffered and composes dirty rows into its private buffer. Wide graphemes use their grid width. Bold, dim, italic, underline, blink, hidden, and strikethrough map to cell attributes. Inverse style and selection swap foreground and background.

The paint path does not compose child Kitty graphics or Sixel images. It draws the character grid only.

While the renderable has focus, the host cursor follows the emulator cursor. It takes the bar, block, or underline style. Hollow block becomes block. It takes blink and RGB cursor color when present. A wide-glyph tail shifts the host cursor one cell left.

`blur()` and `destroy()` hide the host cursor.

Compose runs in the renderable's `renderSelf` [paint hook](/docs/extend/custom-renderables#use-paint-hooks). In each frame that paints the renderable, Core runs `renderBefore`, then compose, then `renderAfter`. All three draw into the private buffer, where `(0, 0)` is the top-left cell of the renderable. Native code then draws the private buffer into the frame at the renderable's position.

If `renderBefore` or `renderAfter` exists, or a hook changed since the last compose, compose redraws the full grid. That redraw replaces `renderBefore` output. Use `renderAfter` to draw over the terminal cells.

`onScreenChange` runs after each successful compose, in every frame that paints the renderable. It is not a dirty-content signal. Do not use it to start work that must run only when the screen content changes.

`invalidate()` forces a full redraw on the next compose.

## Limits and failures

The reply queue keeps at most 1 MiB and drops an overflowing reply. `write()` still delivers replies that were already queued and does not throw.

Construction validates `cols`, `rows`, and `maxScrollback` before it creates the native emulator. A failed constructor destroys the renderable and rethrows. `destroy()` and a failed constructor hide the host cursor. If the renderable has focus, `destroy()` can send a focus-lost sequence to `onData` before it frees the handle. After that, methods return without writing. Encoders return empty bytes.

If framebuffer allocation fails, the renderable does not compose into the parent. `write()` still updates native state.

## Test

```typescript
import { EmbeddedTerminalRenderable } from "@opentui/core"
import { createTestRenderer } from "@opentui/core/testing"

const setup = await createTestRenderer({ width: 40, height: 8 })

try {
  const terminal = new EmbeddedTerminalRenderable(setup.renderer, { width: 20, height: 4 })
  setup.renderer.root.add(terminal)
  terminal.write("hello \x1b[1;32mworld\x1b[0m\r\n")
  await setup.renderOnce()

  console.log(setup.captureCharFrame())
  console.log(terminal.screen().text)
} finally {
  setup.renderer.destroy()
}
```

Use `encodeKey()` and `encodePaste()` to assert mode-aware sequences without a child process. See [Testing](/docs/core-concepts/testing) for `createTestRenderer()`, `mockInput`, and `mockMouse`.

## Options and state

| Member                  | Type                                                             | Description                                                                                     |
| ----------------------- | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `cols`                  | `number`                                                         | Constructor option. Initial emulator columns. Not an instance field.                            |
| `rows`                  | `number`                                                         | Constructor option. Initial emulator rows. Not an instance field.                               |
| `maxScrollback`         | `number`                                                         | Constructor option. Scrollback budget in bytes. Default `10000`.                                |
| `selectable`            | `boolean`                                                        | Whether the renderable joins renderer selection. Default `true`.                                |
| `transparentBackground` | `boolean`                                                        | Use the host terminal's default background for the child's default background. Default `false`. |
| `onData`                | `(data: Uint8Array, source: EmbeddedTerminalDataSource) => void` | Called with encoded input or drained replies.                                                   |
| `onTerminalResize`      | `(cols: number, rows: number) => void`                           | Called after a visible layout size change resizes the emulator.                                 |
| `onScreenChange`        | `() => void`                                                     | Called after each successful compose.                                                           |
| `write(data)`           | `(data: string \| Uint8Array) => void`                           | Parse VT bytes and request a render.                                                            |
| `encodeKey(key)`        | `(key: KeyEvent) => Uint8Array`                                  | Encode a key with the current keyboard mode.                                                    |
| `encodePaste(bytes)`    | `(bytes: Uint8Array) => Uint8Array`                              | Encode a paste with the current paste mode.                                                     |
| `handleKeyPress(key)`   | `(key: KeyEvent) => boolean`                                     | Encode a key and send nonempty bytes as `"input"`.                                              |
| `handlePaste(event)`    | `(event: PasteEvent) => void`                                    | Encode a paste and send nonempty bytes as `"input"`.                                            |
| `focus()` / `blur()`    | `() => void`                                                     | Move focus and send focus sequences when the child enabled them.                                |
| `screen()`              | `() => EmbeddedTerminalScreen`                                   | Painted text and native cursor. `columns` and `rows` are the layout size.                       |
| `hasSelection()`        | `() => boolean`                                                  | Whether this renderable currently has an active selection.                                      |
| `getSelectedText()`     | `() => string`                                                   | Selected emulator text, or `""`.                                                                |
| `invalidate()`          | `() => void`                                                     | Force a full redraw on the next compose.                                                        |

You can assign `onData`, `onTerminalResize`, `onScreenChange`, and `transparentBackground` after construction.

## React and Solid

React and Solid do not register an element for this renderable. Use the Core API, or register your own catalogue entry with `extend()`.

Solid constructs registered classes with `{ id }` only. `maxScrollback`, `cols`, and `rows` then take their defaults. Use an adapter if those values must differ. See [Custom renderables](/docs/extend/custom-renderables#register-framework-elements).

## Related

Read [Interaction, focus, and selection](/docs/core-concepts/interaction) for focus and selection ownership. Read [Keyboard input](/docs/core-concepts/keyboard) for listener order and paste events. Read [Testing](/docs/core-concepts/testing) for frame capture.

---

### Packages Web Src Content Docs Components Frame Buffer (`packages/web/src/content/docs/components/frame-buffer.mdx`)

---
title: FrameBuffer
description: Draw cells, text, images, and effects into a custom rendering surface
---

# FrameBuffer

`FrameBuffer` exposes a low-level, two-dimensional cell buffer for custom graphics. Use [Image](/docs/components/image) to display encoded images without direct cell drawing.

## Availability

| Field           | Availability            |
| --------------- | ----------------------- |
| Package         | `@opentui/core`         |
| Core renderable | `FrameBufferRenderable` |
| React           | Unavailable             |
| Solid           | Unavailable             |
| Status          | Advanced                |

## Basic usage

### Renderable API

```typescript
import { FrameBufferRenderable, RGBA, createCliRenderer } from "@opentui/core"

const renderer = await createCliRenderer()

const canvas = new FrameBufferRenderable(renderer, {
  id: "canvas",
  width: 50,
  height: 20,
})

// Draw on the frame buffer
canvas.frameBuffer.fillRect(5, 2, 20, 10, RGBA.fromHex("#FF0000"))
canvas.frameBuffer.drawText("Hello!", 8, 6, RGBA.fromHex("#FFFFFF"))

renderer.root.add(canvas)
```

The renderable creates `frameBuffer` in the renderer's Context. Native code composes it into each frame at the
renderable's position. Call `requestRender()` after you draw so that the renderer paints the change.

When layout changes the renderable's size, the renderable resizes `frameBuffer`. A resize clears all cells, so draw
again after it.

Destroying the renderable also destroys its current `frameBuffer`. You can assign another buffer to `frameBuffer`.
Create it with `owner: renderer.nativeScene`. The renderable does not destroy the buffer that you replace.

## Drawing methods

### setCell

Set a single cell's content and colors:

```typescript
canvas.frameBuffer.setCell(
  x, // X position
  y, // Y position
  char, // One-cell scalar to display
  fg, // Foreground color (RGBA)
  bg, // Background color (RGBA)
  attributes, // Text attributes (optional, default: 0)
)

// Example
canvas.frameBuffer.setCell(10, 5, "@", RGBA.fromHex("#FFFF00"), RGBA.fromHex("#000000"))
```

`setCell()` uses only the first code point and does not reserve continuation cells. Use it for one-cell scalars. Use `drawText()` for wide or joined graphemes.

Draw text and block characters directly to build a compact chart:

```text terminal=frame-buffer-draw
network throughput
rx  ▂▄▆█▇▅▃▂  42 MB/s
tx  ▃▅▇█▆▄▂▁  18 MB/s
    8 seconds     now
```

### setCellWithAlphaBlending

Set a cell with alpha blending for transparency effects:

```typescript
const semiTransparent = RGBA.fromValues(1.0, 0.0, 0.0, 0.5)
const transparent = RGBA.fromValues(0, 0, 0, 0)
canvas.frameBuffer.setCellWithAlphaBlending(10, 5, " ", transparent, semiTransparent)
```

### drawText

Draw a string of text at a position:

```typescript
canvas.frameBuffer.drawText(
  text, // String to draw
  x, // Starting X position
  y, // Y position
  fg, // Text color (RGBA)
  bg, // Background color (RGBA, optional)
  attributes, // Text attributes (optional, default: 0)
)

// Example
canvas.frameBuffer.drawText("Score: 100", 2, 1, RGBA.fromHex("#00FF00"))
```

### fillRect

Fill a rectangular area with a color:

```typescript
canvas.frameBuffer.fillRect(
  x, // X position
  y, // Y position
  width, // Rectangle width
  height, // Rectangle height
  color, // Fill color (RGBA)
)

// Example: Draw a red rectangle
canvas.frameBuffer.fillRect(10, 5, 20, 8, RGBA.fromHex("#FF0000"))
```

### drawFrameBuffer

Composite another buffer onto this one. The source must be an owned buffer in the same Context:

```typescript
canvas.frameBuffer.drawFrameBuffer(
  destX, // Destination X
  destY, // Destination Y
  sourceBuffer, // Source FrameBuffer (OptimizedBuffer)
  sourceX, // Source X offset (optional)
  sourceY, // Source Y offset (optional)
  sourceWidth, // Width to copy (optional)
  sourceHeight, // Height to copy (optional)
)
```

### drawImage

Place a native image into destination cells:

```typescript
const placed = canvas.frameBuffer.drawImage(
  image,
  x,
  y,
  width,
  height,
  pixelWidth,
  pixelHeight,
  sourceX,
  sourceY,
  sourceWidth,
  sourceHeight,
  protocol,
)
```

`x`, `y`, `width`, and `height` use terminal cells. `pixelWidth` and `pixelHeight` use terminal pixels and default to `0`. Source coordinates default to the full image, and `protocol` defaults to `"auto"`. Sixel requires nonzero pixel dimensions or falls back to blocks.

`false` means the buffer recorded no visible valid placement. A successful placement retains the native image until the buffer is cleared, resized, or destroyed. Ownership does not transfer. The caller must still dispose its `NativeImage`.

### colorMatrix / colorMatrixUniform

Apply native 4x4 RGBA matrix transforms for post-processing effects. Use `colorMatrixUniform` for full-buffer
transforms, and `colorMatrix` when you want to target specific cells.

```typescript
import { INVERT_MATRIX, TargetChannel } from "@opentui/core"

// Full-buffer transform
canvas.frameBuffer.colorMatrixUniform(INVERT_MATRIX, 1.0, TargetChannel.Both)

// Per-cell transform with explicit mask
const cellMask = new Float32Array([10, 5, 1.0, 11, 5, 0.5])
canvas.frameBuffer.colorMatrix(INVERT_MATRIX, cellMask, 1.0, TargetChannel.FG)
```

See [Color matrix reference](/docs/reference/color-matrix) for matrix layout, mask format, and behavior details.

## Properties

| Property                         | Type                                     | Default      | Description                                         |
| -------------------------------- | ---------------------------------------- | ------------ | --------------------------------------------------- |
| `width`                          | `number`                                 | -            | Initial buffer and layout width in cells (required) |
| `height`                         | `number`                                 | -            | Initial buffer and layout height in rows (required) |
| `respectAlpha`                   | `boolean`                                | `false`      | Always blend the buffer's cells into the frame      |
| `position`                       | `string`                                 | `"relative"` | Positioning mode                                    |
| `left`, `top`, `right`, `bottom` | `number`, `"auto"`, or percentage string | -            | Position offsets                                    |

## Example: game canvas

```typescript
import { FrameBufferRenderable, RGBA, createCliRenderer } from "@opentui/core"

const renderer = await createCliRenderer()

const gameCanvas = new FrameBufferRenderable(renderer, {
  id: "game",
  width: 40,
  height: 20,
  position: "absolute",
  left: 5,
  top: 2,
})

// Game state
let playerX = 20
let playerY = 10

function render() {
  const fb = gameCanvas.frameBuffer
  const BG = RGBA.fromHex("#111111")

  // Clear the canvas
  fb.fillRect(0, 0, 40, 20, BG)

  // Draw border
  for (let x = 0; x < 40; x++) {
    fb.setCell(x, 0, "-", RGBA.fromHex("#444444"), BG)
    fb.setCell(x, 19, "-", RGBA.fromHex("#444444"), BG)
  }
  for (let y = 0; y < 20; y++) {
    fb.setCell(0, y, "|", RGBA.fromHex("#444444"), BG)
    fb.setCell(39, y, "|", RGBA.fromHex("#444444"), BG)
  }

  // Draw player
  fb.setCell(playerX, playerY, "@", RGBA.fromHex("#00FF00"), BG)

  // Draw score
  fb.drawText("Score: 0", 2, 0, RGBA.fromHex("#FFFF00"))
}

// Handle input
renderer.keyInput.on("keypress", (key) => {
  switch (key.name) {
    case "up":
      playerY = Math.max(1, playerY - 1)
      break
    case "down":
      playerY = Math.min(18, playerY + 1)
      break
    case "left":
      playerX = Math.max(1, playerX - 1)
      break
    case "right":
      playerX = Math.min(38, playerX + 1)
      break
  }
  render()
  gameCanvas.requestRender()
})

render()
renderer.root.add(gameCanvas)
```

## Example: progress bar

```typescript
const EMPTY_BG = RGBA.fromHex("#222222")

function drawProgressBar(fb, x, y, width, progress, color) {
  const filled = Math.floor(width * progress)

  // Draw filled portion
  for (let i = 0; i < filled; i++) {
    fb.setCell(x + i, y, "█", color, EMPTY_BG)
  }

  // Draw empty portion
  for (let i = filled; i < width; i++) {
    fb.setCell(x + i, y, "░", RGBA.fromHex("#333333"), EMPTY_BG)
  }
}

// Usage
drawProgressBar(canvas.frameBuffer, 5, 10, 30, 0.75, RGBA.fromHex("#00FF00"))
```

A 20-cell bar with 14 filled cells renders as:

```text terminal=frame-buffer-progress
Downloading package
██████████████░░░░░░  70%
14 of 20 files
```

## Related APIs

Read the [Buffer API](/docs/reference/buffer-api) for `OptimizedBuffer` ownership and drawing operations. Use [`NativeImage`](/docs/reference/native-image) to decode and transform image data. The [color matrix reference](/docs/reference/color-matrix) covers post-processing matrix and mask formats.

---

### Packages Web Src Content Docs Components Image (`packages/web/src/content/docs/components/image.mdx`)

---
title: Image
description: Load and render images with Kitty, Sixel, or Unicode blocks
skill:
  entry: true
  intents: [image, image-renderable, image-display, kitty, sixel]
---

# Image

`ImageRenderable` displays PNG, JPEG, WebP, or GIF data from an encoded source. It also accepts an existing `NativeImage`. The renderable uses Kitty graphics, Sixel, or a Unicode block fallback.

Use [`NativeImage`](/docs/reference/native-image) when you need to decode, inspect, or transform image pixels before display.

## Availability

| Field           | Availability          |
| --------------- | --------------------- |
| Package         | `@opentui/core`       |
| Core renderable | `ImageRenderable`     |
| React           | `<image>` (automatic) |
| Solid           | `<image>` (automatic) |
| Status          | Built in              |

## Renderable API

```typescript
import { ImageRenderable, createCliRenderer } from "@opentui/core"

const renderer = await createCliRenderer()
const image = new ImageRenderable(renderer, {
  id: "cover",
  source: "./cover.webp",
  width: 40,
  height: 15,
  fit: "cover",
  protocol: "auto",
  onError: console.error,
})

renderer.root.add(image)
await image.loadPromise
```

`source` accepts every `ImageSource` form: a path, `file:`/HTTP(S)/`blob:`/`data:` URL, `URL`, `Blob`, `Response`, `Uint8Array`, or `ArrayBuffer`. It also accepts `NativeImage`. Format detection uses encoded bytes when the source needs decoding. Encoded sources decode into the renderer's Context, so displaying them needs no copy between Contexts. `ImageRenderable` retains an existing `NativeImage`. You still own and must dispose the source reference.

## React

```tsx
import { createCliRenderer } from "@opentui/core"
import { createRoot } from "@opentui/react"

const renderer = await createCliRenderer()
createRoot(renderer).render(
  <image source="./cover.webp" fit="cover" protocol="auto" style={{ width: 40, height: 15 }} />,
)
```

## Solid

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

const renderer = await createCliRenderer()
await render(
  () => <image source="./cover.webp" fit="cover" protocol="auto" style={{ width: 40, height: 15 }} />,
  renderer,
)
```

You can update `source`, callbacks, fit, and protocol after construction. Replacing `source` keeps the current image visible until the replacement succeeds. It cancels obsolete loading and disposes stale native images. Setting `source` to `undefined` clears it. Clearing `fit` or `protocol` restores `"fit"` or `"auto"`.

## Sizing

| `fit`   | Behavior                                                     |
| ------- | ------------------------------------------------------------ |
| `fit`   | Contain and center the full image. Preserve aspect (default) |
| `cover` | Fill the renderable, preserve aspect ratio, and center-crop  |
| `fill`  | Fill the renderable and allow stretching                     |

Sizing uses terminal pixel resolution when available and a 2:1 cell-height fallback otherwise. During startup and resize, Sixel images temporarily use blocks until current pixel geometry arrives.

## Rendering protocol

| `protocol` | Behavior                                                            |
| ---------- | ------------------------------------------------------------------- |
| `auto`     | Global override, then Kitty, then Sixel, then Unicode blocks        |
| `kitty`    | Force Kitty graphics                                                |
| `sixel`    | Force Sixel. Falls back to blocks without terminal pixel resolution |
| `blocks`   | Portable Unicode quadrant-block rendering                           |

The `blocks` protocol renders generated RGBA pixels as terminal cells:

```text terminal=image-blocks
████████████████████████
██████████████████▘██▜██
█████▛▀▝▀█████████▖██▟██
██▛▀██████▝▀█▛▀▘██▀▀████
▀███████████▗▄▙▄██████▀▀
████████▗▄▟███████▄▄████
▄▄▄▄▄▄▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄▄
████████████████████████
```

With global and per-image protocols set to `auto`, tmux uses blocks. Explicit Kitty, or Sixel with pixel resolution, uses tmux passthrough.

Overlapping images must use the same effective protocol. OpenTUI does not support layering or alpha composition across different effective protocols. Leave overlapping images on `auto` or give them the same explicit `protocol`. Non-overlapping images can use different protocols.

Kitty preserves image alpha, Sixel treats alpha below 128 as transparent, and blocks blend sampled alpha. Placement opacity scales Kitty and block alpha. Sixel dims toward cell backgrounds. Direct, unbuffered fills, text, and box borders cover images at whole-cell granularity without blending.

Use `OPENTUI_IMAGE_PROTOCOL=auto|kitty|sixel|blocks` to set the global default. `OPENTUI_GRAPHICS=false` disables Kitty and Sixel detection. See [Environment variables](/docs/reference/env-vars).

Split-footer scrollback snapshots use the same protocol resolution as live images. They use Kitty placement, Sixel with detected pixel geometry, or Unicode quadrant blocks. Snapshots with mixed effective protocols, overlapping images, or covered Sixel or Kitty cells use blocks. Native scrollback images can render before the footer is pinned. Each image must fit within the output width and the height available above the pinned footer. Its snapshot must start at the beginning of a line. Await `loadPromise` before rendering an image into a `ScrollbackSurface`. See [Writing to scrollback](/docs/core-concepts/renderer#writing-to-scrollback).

`resolveImageRenderProtocol(requested, capabilities, hasResolution)` exposes the same protocol-resolution policy for code that needs it without constructing a renderable.

## Kitty transport

Set `kittyImageTransport` when you create the renderer, or assign `renderer.kittyImageTransport` to change it at runtime. It controls image transmission, not protocol selection or placement. Kitty commands still use the renderer's output backend.

```typescript
const renderer = await createCliRenderer({ kittyImageTransport: "zlib" })
renderer.kittyImageTransport = "file"
// After a transmission, inspect the actual choice rather than assuming the request succeeded.
const status = renderer.kittyImageTransportStatus
```

- `"raw"` is the default. It sends existing encoded PNG data unchanged. Otherwise, opaque pixels use RGB24, and other pixels use RGBA32.
- `"zlib"` tries native level-1 compression. It uses raw if compression expands the stream, exceeds the 64 MiB preparation limit, or cannot allocate buffers. Encoded PNG data stays unchanged.
- `"file"` writes a temporary file and sends its path for the local terminal to read. It still copies pixels, without shared memory or GPU sharing.

A transport change retransmits visible Kitty images without replacing their sources. File negotiation remains asynchronous. When you select an inline mode, outstanding files stay alive until their acknowledgments or deadlines, so queued output can still use them. When you switch back to file, the renderer reuses the session's probe result. It does not reset a failed or cancelled session.

Scrollback snapshots never use file transport because the renderer does not retain their sources for retries. They use self-contained raw or PNG output, or zlib when requested.

### File transport

File transport requires a compatible local POSIX terminal. Windows, remote sessions, disabled graphics, and multiplexers use raw/PNG instead. Custom stdout defaults to remote. Set `remote: false` only if it connects to a local terminal.

File transport requires both a successful `t=f` query with a real file and an upload acknowledgment with an explicit image ID. Older WezTerm versions that answer queries but omit upload acknowledgments stay on raw/PNG. These checks are asynchronous, so initial frames can use raw/PNG while `fileState` is `"probing"`.

Each file has a unique name, permissions `0600`, and contents that do not change. The renderer uses the `TMPDIR` value forwarded at creation, or `/tmp` when absent. If you override `forwardEnvKeys`, include `TMPDIR`.

Each renderer allows at most eight pending files and 64 MiB of pending data. If an image ID is busy or the renderer reaches either limit, it uses raw/PNG without replacing pending files.

A matching Kitty reply lets the renderer remove the file. Frame events, stdout drain, and cursor reports do not confirm that the terminal read or displayed an image.

The renderer checks each file's five-second deadline during rendering and once per second while idle. A timeout does not confirm that the terminal read the file.

Timeouts, terminal or output errors, `cancelKittyImageTransport()`, suspend, and destroy disable file uploads and attempt to remove pending files. Active images retry with raw/PNG after a failed upload. Late replies cannot re-enable file transport. Cleanup failures or termination that bypasses cleanup, such as `SIGKILL`, can leave files behind.

### Transport status

`kittyImageTransportStatus` reports `requested`, `effective`, `fileState`, `fallback`, `pendingFiles`, and `pendingBytes`. The `effective` value describes the last transmission: `raw`, `zlib`, `png`, or `file`. The `compression` fallback means compression did not save space or was unavailable. File transport preserves PNG payloads, but still reports `effective: "file"`.

## Options and state

| Member               | Type                                                        | Description                                                     |
| -------------------- | ----------------------------------------------------------- | --------------------------------------------------------------- |
| `source`             | `ImageRenderableSource`                                     | Encoded image source or `NativeImage`. Optional                 |
| `fit`                | `"fit" \| "cover" \| "fill"`                                | Destination sizing                                              |
| `protocol`           | `"auto" \| "kitty" \| "sixel" \| "blocks"`                  | Requested rendering protocol                                    |
| `onLoad`             | `(image: NativeImage) => void`                              | Called after the current source loads                           |
| `onError`            | `(error: unknown) => void`                                  | Called when the current source fails                            |
| `image`              | `NativeImage \| null`                                       | Currently displayed renderable-owned image                      |
| `loading`            | `boolean`                                                   | Whether the current source is loading                           |
| `loadError`          | `unknown`                                                   | Current load error, otherwise `null`                            |
| `loadPromise`        | `Promise<void> \| null`                                     | Settles after the current load attempt                          |
| `effectiveProtocol`  | `kitty \| sixel \| blocks`                                  | Current resolved protocol. Can change with capabilities or size |
| `cellAspectRatio`    | `number`                                                    | Physical or fallback cell aspect ratio                          |
| `getFittedSize(...)` | `(width, height, cellAspect?, sourceWidth?, sourceHeight?)` | Resolve destination cells. Omitted overrides use current values |

The renderable owns `image` and the `NativeImage` passed to `onLoad`. Do not dispose or transfer them. Images loaded from an encoded source, and images retained or derived from them, belong to the renderer's Context and stop working when the renderer is destroyed. To keep an image beyond the renderer, create it yourself (for example with `NativeImage.load()`) and pass it as `source`. Current-source failures set `loadError`, call `onError`, and resolve `loadPromise`. Superseded, cleared, or destroyed loads resolve without callbacks. Exceptions from either callback reject after state settles. Successful replacement, clearing, and destruction release owned images. A failed replacement keeps the current image.

## Related components

Use [FrameBuffer](/docs/components/frame-buffer) when you need direct cell drawing or `OptimizedBuffer.drawImage()`. Use [QR code](/docs/components/qr-code) for text encoded as a scannable symbol.

---

### Packages Web Src Content Docs Components Input (`packages/web/src/content/docs/components/input.mdx`)

---
title: Input
description: Accept one line of text with cursor, focus, and submit events
skill:
  entry: true
  intents: [input, form, editing, focus]
---

# Input

`Input` edits one line of text and supplies a cursor, placeholder, and focus styles. Use [Textarea](/docs/components/textarea) for multi-line editing.

Focus the input to receive keyboard input.

## Availability

| Field           | Availability          |
| --------------- | --------------------- |
| Package         | `@opentui/core`       |
| Core renderable | `InputRenderable`     |
| React           | `<input>` (automatic) |
| Solid           | `<input>` (automatic) |
| Status          | Built in              |

## Basic usage

### Renderable API

```typescript
import { InputRenderable, InputRenderableEvents, createCliRenderer } from "@opentui/core"

const renderer = await createCliRenderer()

const input = new InputRenderable(renderer, {
  id: "name-input",
  width: 25,
  placeholder: "Enter your name...",
})

input.on(InputRenderableEvents.CHANGE, (value) => {
  console.log("Input value:", value)
})

input.focus()
renderer.root.add(input)
```

An empty input displays its placeholder:

```text terminal=input-placeholder surface
Name
Enter your name
```

## Focus states

The input uses its focused colors while it has focus:

```typescript
const input = new InputRenderable(renderer, {
  id: "styled-input",
  width: 30,
  placeholder: "Type here...",
  backgroundColor: "#1a1a1a",
  focusedBackgroundColor: "#2a2a2a",
  textColor: "#FFFFFF",
  cursorColor: "#00FF00",
})
```

After focusing the input and typing a name:

```text terminal=input-focused surface
Name
Ada Lovelace
```

## Events

### Input event

Emitted after these operations:

- Typing, paste, or `insertText()` inserts text.
- A delete key, a delete method such as `deleteCharBackward()`, `undo()`, or `redo()` runs.
- You assign a different `value`.

The listener receives the current value. Other buffer methods, such as `setText()`, `insertChar()`, `deleteSelection()`,
and `deleteRange()`, do not emit it:

```typescript
import { InputRenderableEvents } from "@opentui/core"

input.on(InputRenderableEvents.INPUT, (value: string) => {
  console.log("Current value:", value)
})
```

### Change event

Emitted on blur, or after a successful `submit()`, when the value differs from the last commit baseline:

```typescript
input.on(InputRenderableEvents.CHANGE, (value: string) => {
  console.log("Value committed:", value)
})
```

The constructor and each `focus()` call set the baseline. Each emitted `CHANGE` event updates it. Thus, blur does not
emit another event if the value did not change after a successful commit.

### Enter event

Emitted when `submit()` succeeds. Return, keypad Enter, and linefeed call `submit()`. If the current UTF-16 code-unit length
is less than `minLength`, `submit()` fails and the input emits no event:

```typescript
input.on(InputRenderableEvents.ENTER, (value: string) => {
  console.log("Submitted value:", value)
})
```

### Getting the current value

```typescript
const currentValue = input.value
```

### Setting the value

```typescript
input.value = "New value"
```

## Properties

| Property                 | Type                                   | Default                                 | Description                                         |
| ------------------------ | -------------------------------------- | --------------------------------------- | --------------------------------------------------- |
| `width`                  | number, `"auto"`, or percentage string | `"auto"`                                | Input field width                                   |
| `value`                  | `string`                               | `""`                                    | Initial text, with newlines removed                 |
| `placeholder`            | `string`                               | `""`                                    | Placeholder text when empty                         |
| `minLength`              | `number`                               | `0`                                     | Minimum UTF-16 code-unit length required for submit |
| `maxLength`              | `number`                               | `1000`                                  | Maximum UTF-16 code-unit length                     |
| `backgroundColor`        | `string \| RGBA`                       | `"transparent"`                         | Background when unfocused                           |
| `focusedBackgroundColor` | `string \| RGBA`                       | `backgroundColor`, else `"transparent"` | Background when focused                             |
| `textColor`              | `string \| RGBA`                       | `"#FFFFFF"`                             | Text color                                          |
| `cursorColor`            | `string \| RGBA`                       | `"#FFFFFF"`                             | Cursor color                                        |
| `position`               | `"relative" \| "absolute"`             | `"relative"`                            | Positioning mode                                    |

## Example: login form

`Input` has no password-masking mode. This example shows the password value as normal terminal text. Do not use this
pattern for real secrets.

```typescript
import { BoxRenderable, InputRenderable, TextRenderable, createCliRenderer } from "@opentui/core"

const renderer = await createCliRenderer()
const form = new BoxRenderable(renderer, {
  width: 40,
  borderStyle: "rounded",
  title: "Login",
  padding: 1,
  gap: 1,
})
const usernameInput = new InputRenderable(renderer, {
  id: "username-input",
  placeholder: "Enter username",
  width: 20,
  backgroundColor: "#222",
  focusedBackgroundColor: "#333",
})
const passwordInput = new InputRenderable(renderer, {
  id: "password-input",
  placeholder: "Enter password",
  width: 20,
  backgroundColor: "#222",
  focusedBackgroundColor: "#333",
})

form.add(new TextRenderable(renderer, { content: "Username:", fg: "#888888" }))
form.add(usernameInput)
form.add(new TextRenderable(renderer, { content: "Password:", fg: "#888888" }))
form.add(passwordInput)
renderer.root.add(form)
usernameInput.focus()
```

## Tab navigation

Add tab navigation between inputs:

```typescript
const inputs = [usernameInput, passwordInput]
let focusIndex = 0

renderer.keyInput.on("keypress", (key) => {
  if (key.name === "tab") {
    focusIndex = (focusIndex + 1) % inputs.length
    inputs[focusIndex].focus()
  }
})
```

## Related concepts

Read [Interaction, focus, and selection](/docs/core-concepts/interaction) for focus and event behavior. Read [Text and terminal cells](/docs/core-concepts/text-and-cells) before enforcing limits that depend on code points, graphemes, or display cells.

---

### Packages Web Src Content Docs Components Line Number (`packages/web/src/content/docs/components/line-number.mdx`)

---
title: Line number gutter
description: Add a line number gutter to code and editor renderables
---

# Line number gutter

`LineNumberRenderable` adds a gutter to a renderable that supplies line information. Use it with [Code](/docs/components/code) or an editor. [Diff](/docs/components/diff) manages its own line-number gutters.

## Availability

| Field           | Availability                                                                 |
| --------------- | ---------------------------------------------------------------------------- |
| Package         | `@opentui/core`                                                              |
| Core renderable | `LineNumberRenderable`                                                       |
| React           | `<line-number>` (automatic)                                                  |
| Solid           | `<line_number>` is runtime built in. Its exact props declaration is missing. |
| Status          | Built in with a Solid typing limitation                                      |

## Basic usage

### Renderable API

```typescript
import {
  CodeRenderable,
  LineNumberRenderable,
  ScrollBoxRenderable,
  SyntaxStyle,
  RGBA,
  createCliRenderer,
} from "@opentui/core"

const renderer = await createCliRenderer()

const syntaxStyle = SyntaxStyle.fromStyles(
  {
    default: { fg: RGBA.fromHex("#E6EDF3") },
  },
  renderer.nativeScene,
)

const code = new CodeRenderable(renderer, {
  id: "code",
  content: "const port = 3000\nserve(port)\nawait ready()",
  filetype: "typescript",
  syntaxStyle,
  width: "100%",
})

const lineNumbers = new LineNumberRenderable(renderer, {
  id: "code-lines",
  target: code,
  minWidth: 3,
  paddingRight: 1,
  fg: "#6b7280",
  bg: "#161b22",
})

const scrollbox = new ScrollBoxRenderable(renderer, {
  id: "scrollbox",
  width: 70,
  height: 18,
})

lineNumbers.setLineSign(1, { before: ">" })
scrollbox.add(lineNumbers)
renderer.root.add(scrollbox)
```

```text terminal=line-number-editor surface
  1 const port = 3000
> 2 serve(port)
  3 await ready()
```

`LineNumberRenderable` adds the gutter and the target as its own children. Add the `LineNumberRenderable` to the tree, not the target.

## Line signs and colors

Line arguments and map keys are zero-based logical line indexes. With the default `lineNumberOffset`, line `0` shows the number `1`.

Set one background color for a line, or set separate gutter and content colors with a `LineColorConfig` object:

```typescript
// Shorthand: applies the color to the gutter and a 20%-darker color to the content
lineNumbers.setLineColor(3, "#2b6cb0")

// Split config: different gutter vs content background
lineNumbers.setLineColor(3, {
  gutter: "#2b6cb0",
  content: "#1f2937",
})

lineNumbers.setLineSign(3, { before: ">", beforeColor: "#2b6cb0" })

// Clear
lineNumbers.clearLineColor(3)
lineNumbers.clearLineSign(3)
```

The shorthand color becomes the gutter background. The content background uses the same color, 20% darker. A `LineColorConfig` with only `gutter` uses the same rule for the content background.

## Theming the gutter

You can change the gutter text and background colors after construction:

```typescript
lineNumbers.fg = "#6b7280"
lineNumbers.bg = "#161b22"
```

Assign `undefined` to restore the defaults: a `#888888` foreground and a transparent background.

### Initial visibility

The constructor ignores the `showLineNumbers` option. To hide the gutter at the start, set the property after construction:

```typescript
const lineNumbers = new LineNumberRenderable(renderer, { target: code })
lineNumbers.showLineNumbers = false
```

## Properties

| Property           | Type                                             | Default     | Description                    |
| ------------------ | ------------------------------------------------ | ----------- | ------------------------------ |
| `target`           | `Renderable & LineInfoProvider`                  | -           | Target renderable to number    |
| `fg`               | `string` or `RGBA`                               | `#888888`   | Gutter text color              |
| `bg`               | `string` or `RGBA`                               | transparent | Gutter background color        |
| `minWidth`         | `number`                                         | `3`         | Minimum gutter width           |
| `paddingRight`     | `number`                                         | `1`         | Right padding for gutter       |
| `lineColors`       | `Map<number, string \| RGBA \| LineColorConfig>` | -           | Per-line background colors     |
| `lineSigns`        | `Map<number, LineSign>`                          | -           | Per-line signs (before/after)  |
| `lineNumberOffset` | `number`                                         | `0`         | Offset for line numbering      |
| `hideLineNumbers`  | `Set<number>`                                    | -           | Lines to hide numbers for      |
| `lineNumbers`      | `Map<number, number>`                            | -           | Override line numbers per line |
| `showLineNumbers`  | `boolean`                                        | `true`      | Toggle gutter visibility       |

## Methods

| Method                 | Description                           |
| ---------------------- | ------------------------------------- |
| `setLineColor()`       | Set a background color for a line     |
| `clearLineColor()`     | Clear a line background color         |
| `setLineSign()`        | Set a sign before/after a line number |
| `clearLineSign()`      | Clear a line sign                     |
| `setLineNumbers()`     | Override multiple line numbers        |
| `setHideLineNumbers()` | Hide line numbers for specific lines  |

## Related components

Use [Code](/docs/components/code) as a syntax-highlighted target. Use [Diff](/docs/components/diff) when you need patch parsing and paired gutters. [Markdown](/docs/components/markdown) owns fenced code blocks. [TextTable](/docs/components/text-table) displays rows and columns without line semantics.

---

### Packages Web Src Content Docs Components Markdown (`packages/web/src/content/docs/components/markdown.mdx`)

---
title: Markdown
description: Render markdown with syntax-aware styling
---

# Markdown

`Markdown` renders document structure and can use Tree-sitter highlighting in fenced code blocks. Use [Code](/docs/components/code) when the full content is source code.

## Availability

| Field           | Availability             |
| --------------- | ------------------------ |
| Package         | `@opentui/core`          |
| Core renderable | `MarkdownRenderable`     |
| React           | `<markdown>` (automatic) |
| Solid           | `<markdown>` (automatic) |
| Status          | Built in                 |

## Basic usage

### Renderable API

````typescript
import { MarkdownRenderable, SyntaxStyle, RGBA, createCliRenderer } from "@opentui/core"

const renderer = await createCliRenderer()

const syntaxStyle = SyntaxStyle.fromStyles(
  {
    "markup.heading.1": { fg: RGBA.fromHex("#58A6FF"), bold: true },
    "markup.list": { fg: RGBA.fromHex("#FF7B72") },
    "markup.raw": { fg: RGBA.fromHex("#A5D6FF") },
    default: { fg: RGBA.fromHex("#E6EDF3") },
  },
  renderer.nativeScene,
)

const markdown = new MarkdownRenderable(renderer, {
  id: "readme",
  width: 60,
  content: "# Hello\n\n- One\n- Two\n\n```ts\nconst x = 1\n```",
  syntaxStyle,
})

renderer.root.add(markdown)
````

## Fenced language normalization

Markdown normalizes each code fence info string to a file type before Tree-sitter highlighting:

- `tsx` -> `typescriptreact`
- `.jsx` -> `javascriptreact`
- `TSX title=Button.tsx` -> `typescriptreact`
- `Dockerfile` -> `dockerfile`

Markdown uses `infoStringToFiletype()` for this step. To add or change mappings at runtime, edit the maps that it reads:

```typescript
import { extensionToFiletype, basenameToFiletype } from "@opentui/core"

extensionToFiletype.set("templ", "html")
basenameToFiletype.set("mytoolrc", "yaml")
```

## Concealment

Set `conceal: true` to hide Markdown markers, such as backticks and emphasis markers:

```typescript
const markdown = new MarkdownRenderable(renderer, {
  content: "**bold** and `code`",
  syntaxStyle,
  conceal: true,
})
```

`concealCode` controls concealment inside fenced code blocks separately. Its default is `false`.

## Streaming updates

Use streaming mode when content arrives in chunks. Keep `streaming: true` while you append chunks.
When the content is complete, set `markdown.streaming = false` to finish parsing the trailing block.
Tables show trailing partial rows, with missing cells shown empty.

```typescript
const markdown = new MarkdownRenderable(renderer, {
  content: "",
  syntaxStyle,
  streaming: true,
})

markdown.content += "# Live log\n"
markdown.content += "- line 1\n"
markdown.streaming = false
```

### Stable block prefix

`parseMarkdownIncremental` reports how many parsed tokens at the head of the stream are unlikely to change after an append. The renderable exposes this value as a count of **top-level render blocks**. You can commit those blocks while the unstable tail changes.

To use it, set `internalBlockMode: "top-level"`. The renderable then keeps each top-level Markdown block as a separate child renderable. These blocks include headings, paragraphs, lists, tables, and fenced code. `markdown._stableBlockCount` gives the current stable prefix length. In the default mode, it is always `0`. This matches the shape that [`ScrollbackSurface`](/docs/core-concepts/renderer#writing-to-scrollback) expects for row-by-row commits.

```typescript
import { MarkdownRenderable, RGBA, SyntaxStyle } from "@opentui/core"

const syntaxStyle = SyntaxStyle.fromStyles(
  {
    default: { fg: RGBA.fromHex("#E6EDF3") },
  },
  renderer.nativeScene,
)

const md = new MarkdownRenderable(renderer, {
  content: "",
  syntaxStyle,
  streaming: true,
  internalBlockMode: "top-level",
})

md.content = "# Title\n\nPara 1"
// md._stableBlockCount is 0. While streaming, the newest parsed tokens stay unstable.

md.content = "# Title\n\nPara 1\n\nPara 2"
// Still 0.

md.content = "# Title\n\nPara 1\n\nPara 2\n\nPara 3"
// md._stableBlockCount is 2: "# Title" and "Para 1" are stable.
```

`internalBlockMode` is an internal, experimental flag for the built-in scrollback streaming demo. This status does not apply to Markdown or to its other options. For normal rendering, keep the default value, `"coalesced"`. This value combines sibling blocks into larger render blocks and keeps the existing layout.

## Markdown tables

Use `tableOptions` to set the style, sizing, wrapping, borders, and selection of Markdown tables.

```typescript
const markdown = new MarkdownRenderable(renderer, {
  content: "| Service | Status |\n| --- | --- |\n| api | ok |",
  syntaxStyle,
  tableOptions: {
    style: "grid",
    widthMode: "full",
    columnFitter: "balanced",
    wrapMode: "word",
    cellPadding: 1,
    cellPaddingX: 2,
    cellPaddingY: 0,
    borders: true,
    outerBorder: true,
    borderStyle: "rounded",
    borderColor: "#6b7280",
    selectable: true,
  },
})
```

### `tableOptions`

| Option         | Type                           | Default                 | Description                                       |
| -------------- | ------------------------------ | ----------------------- | ------------------------------------------------- |
| `style`        | `"grid" \| "columns"`          | depends on block mode   | Visual preset (see [Table styles](#table-styles)) |
| `widthMode`    | `"content" \| "full"`          | depends on style        | `"full"` expands columns to fill available width  |
| `columnFitter` | `"proportional" \| "balanced"` | `"proportional"`        | How columns shrink when space is constrained      |
| `wrapMode`     | `"none" \| "char" \| "word"`   | `"word"`                | Wrapping mode inside each table cell              |
| `cellPadding`  | `number`                       | `0`                     | Padding on all sides of each cell                 |
| `cellPaddingX` | `number`                       | `cellPadding`           | Horizontal padding on each side                   |
| `cellPaddingY` | `number`                       | `cellPadding`           | Vertical padding on each side                     |
| `borders`      | `boolean`                      | depends on style        | Enable inner and outer borders                    |
| `outerBorder`  | `boolean`                      | `borders`               | Override outer border visibility                  |
| `borderStyle`  | `BorderStyle`                  | `"single"`              | Table border character set                        |
| `borderColor`  | `ColorInput`                   | conceal fg or `#888888` | Border color for markdown tables                  |
| `selectable`   | `boolean`                      | `true`                  | Enable table cell text selection                  |

### Table styles

`tableOptions.style` picks a preset. The preset sets the defaults for `borders`, `outerBorder`, and `widthMode`:

- **`"grid"`**: a boxed table with visible borders. Defaults to `borders: true` and `widthMode: "full"`. This is the normal table rendering.
- **`"columns"`**: borderless columns with a 2-column gap. Defaults to `borders: false` and `widthMode: "content"`. Use it for append-only output where a full-width grid is too heavy.

This Name/Status sample shows both presets:

Grid:

```text terminal=markdown-table-grid
┌────────┬────────┐
│ Name   │ Status │
├────────┼────────┤
│ api    │ ready  │
├────────┼────────┤
│ worker │ paused │
└────────┴────────┘
```

Columns:

```text terminal=markdown-table-columns
Name    Status
api     ready
worker  paused
```

If you do not pass `style`, it is `"columns"` when `internalBlockMode` is `"top-level"`, and `"grid"` otherwise. Individual fields, such as `borders: true`, override the preset defaults.

## Custom node rendering

Use `renderNode` to replace the renderable for a block. Return `undefined` or `null` to use the default renderable. Call `context.defaultRender()` to create the default renderable yourself:

```typescript
const markdown = new MarkdownRenderable(renderer, {
  content: "# Title\n\nHello",
  syntaxStyle,
  renderNode: (token, context) => {
    if (token.type === "heading") {
      return context.defaultRender()
    }
    return undefined
  },
})
```

### Custom fenced-code languages

Use `createMarkdownCodeBlockRenderer` to replace only specific fenced-code languages. It matches each key against the normalized info string. A `tsx` fence matches the key `typescriptreact`. A custom name such as `taskflow` matches itself.

````typescript
import {
  BoxRenderable,
  MarkdownRenderable,
  SyntaxStyle,
  TextRenderable,
  createMarkdownCodeBlockRenderer,
  type CliRenderer,
  type MarkdownCodeBlockRenderer,
} from "@opentui/core"

const syntaxStyle = SyntaxStyle.fromStyles({ default: {} }, renderer.nativeScene)

const renderTaskFlow =
  (renderer: CliRenderer): MarkdownCodeBlockRenderer =>
  (token) => {
    const steps = token.text
      .split("\n")
      .filter((line) => line.startsWith("step "))
      .map((line) => line.slice("step ".length))

    const card = new BoxRenderable(renderer, {
      border: true,
      borderStyle: "rounded",
      borderColor: "#38BDF8",
      paddingX: 1,
      flexDirection: "column",
      width: "100%",
    })

    for (const step of steps) {
      card.add(new TextRenderable(renderer, { content: `- ${step}`, width: "100%" }))
    }

    return card
  }

const markdown = new MarkdownRenderable(renderer, {
  content: "```taskflow\nstep Parse markdown done\nstep Render widget active\n```",
  syntaxStyle,
  renderNode: createMarkdownCodeBlockRenderer({
    taskflow: renderTaskFlow(renderer),
  }),
})
````

## Properties

| Property            | Type                                                                            | Default       | Description                                                   |
| ------------------- | ------------------------------------------------------------------------------- | ------------- | ------------------------------------------------------------- |
| `content`           | `string`                                                                        | `""`          | Markdown source                                               |
| `syntaxStyle`       | `SyntaxStyle`                                                                   | required      | Style definitions for tokens                                  |
| `fg`                | `ColorInput`                                                                    | -             | Base foreground color, also used by inner code blocks         |
| `bg`                | `ColorInput`                                                                    | -             | Base background color, also used by inner code blocks         |
| `conceal`           | `boolean`                                                                       | `true`        | Hide markdown markers in markdown text                        |
| `concealCode`       | `boolean`                                                                       | `false`       | Hide markers inside fenced code blocks                        |
| `streaming`         | `boolean`                                                                       | `false`       | Incremental mode. Set `false` to finalize                     |
| `tableOptions`      | `MarkdownTableOptions`                                                          | -             | Options for markdown table rendering                          |
| `internalBlockMode` | `"coalesced" \| "top-level"`                                                    | `"coalesced"` | Experimental: expose top-level blocks as separate renderables |
| `treeSitterClient`  | `TreeSitterClient`                                                              | -             | Custom Tree-sitter client for code blocks                     |
| `renderNode`        | `(token: Token, context: RenderNodeContext) => Renderable \| null \| undefined` | -             | Custom renderer for each Markdown block                       |

Markdown rebuilds its blocks at once when you set `content`.
It applies `syntaxStyle`, `fg`, `bg`, `conceal`, and `concealCode` changes later, in its lifecycle pass (`onLifecyclePass`).
Core runs lifecycle passes after the frame callbacks and before layout, so the new styles appear in that frame.
These five setters do not request a frame. If nothing else requests one, call `markdown.requestRender()`.

If you assign `onLifecyclePass` on a Markdown renderable, even to `null`, Markdown no longer applies these changes by itself.
Call `refreshStyles()` after each change. `refreshStyles()` rebuilds the blocks at once and requests a frame.

OpenTUI bundles a limited parser set. See the [Tree-sitter reference](/docs/reference/tree-sitter) before you highlight other fenced-code languages.

## Related components

Use [Code](/docs/components/code) for source-only content. Add a [Line number gutter](/docs/components/line-number) to a compatible code or editor renderable. Use [Diff](/docs/components/diff) for patches. Use [TextTable](/docs/components/text-table) for table data that does not start as Markdown.

---

### Packages Web Src Content Docs Components Qr Code (`packages/web/src/content/docs/components/qr-code.mdx`)

---
title: QR code
description: Display a scannable QR code from text content
---

# QR code

`QRCodeRenderable` displays text or URLs as a QR Code Model 2 symbol. It uses half-block cells to keep the symbol square in terminal cell geometry.

For module matrices, terminal strings, SVG, and raw bytes, use the standalone [QR encoder](/docs/reference/qr-encoder).
The same package exports this encoder. It also supports explicit segments, ECI, GS1/FNC1, and structured append.

## Availability

| Field           | Availability                                                                         |
| --------------- | ------------------------------------------------------------------------------------ |
| Package         | Core `@opentui/qrcode`, React `@opentui/qrcode/react`, Solid `@opentui/qrcode/solid` |
| Core renderable | `QRCodeRenderable`                                                                   |
| React           | Call `registerQRCode()` once, then use `<qr-code>`                                   |
| Solid           | Call `registerQRCode()` once, then use `<qr_code>`                                   |
| Status          | Separately registered                                                                |

Install the QR code package separately:

```bash
bun add @opentui/qrcode
```

## Basic usage

### Renderable API

```typescript
import { createCliRenderer } from "@opentui/core"
import { QRCodeRenderable } from "@opentui/qrcode"

const renderer = await createCliRenderer()

const qr = new QRCodeRenderable(renderer, {
  id: "docs-link",
  content: "https://opentui.com/docs",
  quietZone: 4,
  scale: 2,
})

renderer.root.add(qr)
```

### React JSX

```tsx
import { createCliRenderer } from "@opentui/core"
import { createRoot } from "@opentui/react"
import { registerQRCode } from "@opentui/qrcode/react"

registerQRCode()

const renderer = await createCliRenderer()
createRoot(renderer).render(
  <qr-code
    content="https://opentui.com/docs"
    quietZone={4}
    scale={2}
    foregroundColor="#111827"
    backgroundColor="#ffffff"
  />,
)
```

### Solid JSX

```tsx
import { render } from "@opentui/solid"
import { registerQRCode } from "@opentui/qrcode/solid"

registerQRCode()

render(() => (
  <qr_code
    content="https://opentui.com/docs"
    quietZone={4}
    scale={2}
    foregroundColor="#111827"
    backgroundColor="#ffffff"
  />
))
```

## Sizing

`QRCodeRenderable` measures itself from the encoded QR version, the quiet zone, and the requested scale. The default `fit: "contain"` lets the QR code shrink to fit constrained parents while preserving a valid square module grid.

```typescript
const qr = new QRCodeRenderable(renderer, {
  content: "opentui.com",
  quietZone: 4,
  scale: 2,
  fit: "contain",
})
```

Use `fit: "none"` when you want the configured scale to be the only rendered size.

`quietZone` must be at least 4 modules for a standard QR code. OpenTUI validates the matrix and quiet-zone geometry, but scanner reliability still depends on terminal font, cell aspect ratio, display contrast, and camera conditions.

The content `OPENTUI` and a four-module quiet zone fit a version-one symbol:

```text terminal=qr-code-version-one


    █▀▀▀▀▀█ ▀█▄▀█ █▀▀▀▀▀█
    █ ███ █ ▀ ▀▀█ █ ███ █
    █ ▀▀▀ █ ▀ ▀█  █ ▀▀▀ █
    ▀▀▀▀▀▀▀ █ ▀▄█ ▀▀▀▀▀▀▀
    ▄▄ ▄ ▀▀▄ █ ▄▀▄▀▄█▄█▄█
    ▄█▀▀█▄▀ █ ▀▀▀▄▄█ ▀█▄
     ▀  ▀ ▀▀█  ██▄ ▀▀ ▀ ▀
    █▀▀▀▀▀█ ▄▄▀ ▀▀▄ ▀█▄█▀
    █ ███ █  ▀███▀█ ▀▄▀
    █ ▀▀▀ █  █ ▀▀▀▀▀█▄▀▀█
    ▀▀▀▀▀▀▀  ▀ ▀   ▀ ▀▀


```

## Fallback content

When a container is too small to render even a scale-1 QR code, show fallback text instead of an empty area:

```typescript
const qr = new QRCodeRenderable(renderer, {
  content: "https://opentui.com/docs",
  fallbackContent: "Resize for QR",
  fallbackColor: "#94a3b8",
})
```

Fallback painting currently indexes UTF-16 code units and writes one cell for each unit. Use one-cell Basic Multilingual
Plane characters. Emoji, joined or combining graphemes, and wide characters can split, disappear, or misalign.

## Error correction

Set `errorCorrectionLevel` with the QR package error correction enum:

```typescript
import { ErrorCorrectionLevel, QRCodeRenderable } from "@opentui/qrcode"

const qr = new QRCodeRenderable(renderer, {
  content: "opentui.com",
  errorCorrectionLevel: ErrorCorrectionLevel.H,
})
```

Higher error correction improves resilience but can increase the QR version and rendered size for longer content.

## Properties

| Property               | Type                   | Default     | Description                                      |
| ---------------------- | ---------------------- | ----------- | ------------------------------------------------ |
| `content`              | `string`               | `""`        | Text content to encode                           |
| `errorCorrectionLevel` | `ErrorCorrectionLevel` | `M`         | QR error correction level                        |
| `quietZone`            | `number`               | `4`         | Blank module border around the QR code           |
| `scale`                | `number`               | `1`         | Terminal columns per QR module before fitting    |
| `fit`                  | `"contain" \| "none"`  | `"contain"` | Whether the QR code may shrink to fit its parent |
| `foregroundColor`      | `ColorInput`           | `"#000000"` | Dark module color                                |
| `backgroundColor`      | `ColorInput`           | `"#ffffff"` | Light module and quiet-zone color                |
| `fallbackContent`      | `string`               | `""`        | Text rendered when the QR code cannot fit        |
| `fallbackColor`        | `ColorInput`           | `"#ffffff"` | Fallback text color                              |

## Related APIs

Use the [QR encoder](/docs/reference/qr-encoder) when you need a matrix or serialized output without a renderable. See [Image](/docs/components/image) for decoded image display.

---

### Packages Web Src Content Docs Components Scrollbar (`packages/web/src/content/docs/components/scrollbar.mdx`)

---
title: ScrollBar
description: Control a scroll position with a standalone bar
---

# ScrollBar

`ScrollBar` controls a scroll position with optional arrows, keyboard input, and a draggable thumb. Use [ScrollBox](/docs/components/scrollbox) when the component must also own and clip scrollable children.

## Availability

| Field           | Availability             |
| --------------- | ------------------------ |
| Package         | `@opentui/core`          |
| Core renderable | `ScrollBarRenderable`    |
| React           | Unavailable              |
| Solid           | Unavailable              |
| Status          | Built-in Core renderable |

## Basic usage

### Renderable API

```typescript
import { ScrollBarRenderable, createCliRenderer } from "@opentui/core"

const renderer = await createCliRenderer()

const scrollbar = new ScrollBarRenderable(renderer, {
  id: "scrollbar",
  orientation: "vertical",
  height: 10,
  showArrows: true,
  trackOptions: {
    backgroundColor: "#222222",
    foregroundColor: "#888888",
  },
  onChange: (position) => {
    console.log("Scroll position:", position)
  },
})

// Connect the scrollbar to your content
scrollbar.scrollSize = 200
scrollbar.viewportSize = 20
scrollbar.scrollPosition = 0

renderer.root.add(scrollbar)
scrollbar.focus()
```

```text terminal=scrollbar-arrows
position: 0 / 180  ▲
                   ▀







viewport: 20 / 200 ▼
```

## Keyboard controls

When focused, the scrollbar responds to:

- `Up`/`Down` or `k`/`j` for vertical bars
- `Left`/`Right` or `h`/`l` for horizontal bars
- `PageUp`/`PageDown` to move by half the viewport
- `Home`/`End` to jump to start/end

## Properties

| Property         | Type                           | Default | Description                            |
| ---------------- | ------------------------------ | ------- | -------------------------------------- |
| `orientation`    | `"vertical"` or `"horizontal"` | -       | Scrollbar direction                    |
| `showArrows`     | `boolean`                      | `false` | Show arrow buttons                     |
| `arrowOptions`   | `ArrowOptions`                 | -       | Styling for arrow buttons              |
| `trackOptions`   | `Partial<SliderOptions>`       | -       | Styling for the track and thumb        |
| `scrollSize`     | `number`                       | `0`     | Total scrollable size                  |
| `viewportSize`   | `number`                       | `0`     | Visible size of the viewport           |
| `scrollPosition` | `number`                       | `0`     | Current scroll position                |
| `scrollStep`     | `number`                       | -       | Step size when `scrollBy(..., "step")` |
| `onChange`       | `(position: number) => void`   | -       | Fired when scroll position changes     |

## Related concepts

Read [Layout](/docs/core-concepts/layout) for viewport dimensions. Read [Interaction, focus, and selection](/docs/core-concepts/interaction) for keyboard and pointer behavior. [Slider](/docs/components/slider) exposes the underlying continuous-value control without scroll semantics.

---

### Packages Web Src Content Docs Components Scrollbox (`packages/web/src/content/docs/components/scrollbox.mdx`)

---
title: ScrollBox
description: Scroll and cull child content in a bounded viewport
---

# ScrollBox

`ScrollBox` scrolls arbitrary child renderables and manages its own [ScrollBar](/docs/components/scrollbar) instances. Use a standalone ScrollBar only when another model owns the scroll position.

## Availability

| Field           | Availability              |
| --------------- | ------------------------- |
| Package         | `@opentui/core`           |
| Core renderable | `ScrollBoxRenderable`     |
| React           | `<scrollbox>` (automatic) |
| Solid           | `<scrollbox>` (automatic) |
| Status          | Built in                  |

## Basic usage

### Renderable API

```typescript
import { ScrollBoxRenderable, TextRenderable, BoxRenderable, createCliRenderer } from "@opentui/core"

const renderer = await createCliRenderer()

const scrollbox = new ScrollBoxRenderable(renderer, {
  id: "scrollbox",
  width: 40,
  height: 20,
})

// Add content to the scrollbox
for (let i = 0; i < 100; i++) {
  scrollbox.add(
    new BoxRenderable(renderer, {
      id: `item-${i}`,
      width: "100%",
      height: 2,
      backgroundColor: i % 2 === 0 ? "#292e42" : "#2f3449",
    }),
  )
}

renderer.root.add(scrollbox)
```

A bordered list of source files initially shows its first five rows:

```text terminal=scrollbox-top
offset: 0 / 7
┌──────────────────────────────┐
│01  src/index.ts             ▀│
│02  src/app.ts                │
│03  src/layout.ts             │
│04  src/theme.ts              │
│05  src/events.ts             │
└──────────────────────────────┘
```

## Sticky scroll

Enable sticky scroll to keep content pinned to an edge as new content arrives. Set both `stickyScroll` and `stickyStart` because `stickyStart` has no default.

```typescript
const scrollbox = new ScrollBoxRenderable(renderer, {
  id: "logs",
  width: 60,
  height: 20,
  stickyScroll: true,
  stickyStart: "bottom", // New content will keep the view scrolled to bottom
})
```

### Sticky positions

- `"bottom"`: Stay scrolled to the bottom
- `"top"`: Stay scrolled to the top
- `"left"`: Stay scrolled to the left
- `"right"`: Stay scrolled to the right

When you scroll away from the sticky position, sticky behavior pauses until you scroll back to the sticky edge.

## Bidirectional scrolling

Enable scrolling in both directions:

```typescript
const scrollbox = new ScrollBoxRenderable(renderer, {
  id: "canvas",
  width: 60,
  height: 30,
  scrollX: true,
  scrollY: true,
})
```

By default, `scrollY` is `true` and `scrollX` is `false`.

## Viewport culling

Viewport culling skips children outside the viewport in large content. It is on by default:

```typescript
const scrollbox = new ScrollBoxRenderable(renderer, {
  id: "large-list",
  width: 40,
  height: 20,
  viewportCulling: true, // Only render visible items
})
```

Culling applies when the content holds 16 or more direct children, including hidden ones, or when the viewport has zero width or height. Native code then skips each direct child that does not overlap the viewport, with its whole subtree. Otherwise, native code paints all children and clips them to the viewport.

Skipped children and their descendants run no paint hooks (`renderBefore`, `renderSelf`, and `renderAfter`) and no update hook (`onUpdate`). Yoga still computes their layout. A skipped direct child still runs its resize hook (`onResize`), but its descendants do not. Do not make layout or state depend on paint or update hooks. Disable `viewportCulling` if every child must run these hooks.

## Customizing scrollbars

Style the scrollbars using nested options:

```typescript
const scrollbox = new ScrollBoxRenderable(renderer, {
  id: "styled-scroll",
  width: 40,
  height: 20,
  scrollbarOptions: {
    showArrows: true,
    trackOptions: {
      foregroundColor: "#7aa2f7",
      backgroundColor: "#414868",
    },
  },
  // Or customize vertical and horizontal separately
  verticalScrollbarOptions: {
    trackOptions: { backgroundColor: "#333" },
  },
  horizontalScrollbarOptions: {
    trackOptions: { backgroundColor: "#333" },
  },
})
```

## Customizing sub-components

ScrollBox contains several internal components that you can style individually:

```typescript
const scrollbox = new ScrollBoxRenderable(renderer, {
  id: "custom-scroll",
  width: 40,
  height: 20,
  rootOptions: {
    backgroundColor: "#24283b",
  },
  wrapperOptions: {
    backgroundColor: "#1f2335",
  },
  viewportOptions: {
    backgroundColor: "#1a1b26",
  },
  contentOptions: {
    backgroundColor: "#16161e",
  },
})
```

## Scroll methods

### scrollBy

Scroll by a relative amount:

```typescript
// Scroll down 5 lines
scrollbox.scrollBy(5)

// Scroll with both x and y
scrollbox.scrollBy({ x: 10, y: 5 })

// Scroll by viewport (page)
scrollbox.scrollBy(1, "viewport")
```

### scrollTo

Scroll to an absolute position:

```typescript
// Scroll to top
scrollbox.scrollTo(0)

// Scroll to specific position
scrollbox.scrollTo({ x: 0, y: 100 })
```

The same list after `scrollTo(5)`:

```text terminal=scrollbox-scrolled
offset: 5 / 7
┌──────────────────────────────┐
│06  src/input.ts              │
│07  src/scroll.ts             │
│08  src/render.ts             │
│09  src/state.ts             ▀│
│10  src/config.ts             │
└──────────────────────────────┘
```

### scrollChildIntoView

Scroll the minimum distance needed to show a nested child in the viewport. The method uses DOM-style "nearest"
behavior. If the child already fits, the call is a no-op. An oversized child also stays in place when it extends beyond
both edges of an axis. Otherwise, the method scrolls to reveal the nearest edge.

```typescript
scrollbox.scrollChildIntoView("table-row-42")
```

Use it when you focus an offscreen element, such as a search result or a new form field.

## Keyboard navigation

When focused, ScrollBox responds to keyboard input:

- **Arrow keys:** Move by one fifth of the active viewport.
- **Page Up/Down:** Move by one half of the vertical viewport.
- **Home/End:** Move to the start or end.

## Properties

| Property                     | Type                                     | Default | Description                            |
| ---------------------------- | ---------------------------------------- | ------- | -------------------------------------- |
| `scrollX`                    | `boolean`                                | `false` | Enable horizontal scrolling            |
| `scrollY`                    | `boolean`                                | `true`  | Enable vertical scrolling              |
| `stickyScroll`               | `boolean`                                | `false` | Keep scroll position pinned to an edge |
| `stickyStart`                | `"top" \| "bottom" \| "left" \| "right"` | -       | Which edge to stick to                 |
| `viewportCulling`            | `boolean`                                | `true`  | Skip children outside the viewport     |
| `scrollAcceleration`         | `ScrollAcceleration`                     | -       | Custom scroll acceleration algorithm   |
| `rootOptions`                | `BoxOptions`                             | -       | Style options for root container       |
| `wrapperOptions`             | `BoxOptions`                             | -       | Style options for wrapper              |
| `viewportOptions`            | `BoxOptions`                             | -       | Style options for viewport             |
| `contentOptions`             | `BoxOptions`                             | -       | Style options for content container    |
| `scrollbarOptions`           | `ScrollBarOptions`                       | -       | Options for both scrollbars            |
| `verticalScrollbarOptions`   | `ScrollBarOptions`                       | -       | Options for vertical scrollbar only    |
| `horizontalScrollbarOptions` | `ScrollBarOptions`                       | -       | Options for horizontal scrollbar only  |

## Additional properties

| Property       | Type     | Description                                  |
| -------------- | -------- | -------------------------------------------- |
| `scrollTop`    | `number` | Current vertical scroll position (get/set)   |
| `scrollLeft`   | `number` | Current horizontal scroll position (get/set) |
| `scrollWidth`  | `number` | Total scrollable width (read-only)           |
| `scrollHeight` | `number` | Total scrollable height (read-only)          |

## Internal components

ScrollBox exposes its internal components for advanced use:

```typescript
scrollbox.wrapper // BoxRenderable - outer wrapper
scrollbox.viewport // BoxRenderable - visible area
scrollbox.content // ContentRenderable - holds children
scrollbox.horizontalScrollBar // ScrollBarRenderable
scrollbox.verticalScrollBar // ScrollBarRenderable
```

## Related concepts

Read [Layout](/docs/core-concepts/layout) for viewport sizing. Read [Interaction, focus, and selection](/docs/core-concepts/interaction) for focus, wheel input, drag selection, and event propagation.

---

### Packages Web Src Content Docs Components Select (`packages/web/src/content/docs/components/select.mdx`)

---
title: Select
description: Choose one option from a vertical list
---

# Select

`Select` shows discrete choices in a vertical list. Use [TabSelect](/docs/components/tab-select) for a horizontal set of peer views or [Slider](/docs/components/slider) for a continuous value.

Focus the select to enable keyboard input.

## Availability

| Field           | Availability           |
| --------------- | ---------------------- |
| Package         | `@opentui/core`        |
| Core renderable | `SelectRenderable`     |
| React           | `<select>` (automatic) |
| Solid           | `<select>` (automatic) |
| Status          | Built in               |

## Basic usage

### Renderable API

```typescript
import { SelectRenderable, SelectRenderableEvents, createCliRenderer } from "@opentui/core"

const renderer = await createCliRenderer()

const menu = new SelectRenderable(renderer, {
  id: "menu",
  width: 32,
  height: 6,
  options: [
    { name: "New file", description: "Create a document" },
    { name: "Open file", description: "Browse existing files" },
    { name: "Save", description: "Write current changes" },
  ],
})

menu.on(SelectRenderableEvents.ITEM_SELECTED, (index, option) => {
  console.log("Selected:", option.name)
})

menu.focus()
renderer.root.add(menu)
```

After pressing Down, the next option is selected:

```text terminal=select-options surface
   New file
   Create a document
 ▶ Open file
   Browse existing files
   Save
   Write current changes
```

## Keyboard navigation

When focused, the select responds to these keys:

| Key                       | Action                |
| ------------------------- | --------------------- |
| `Up` / `k`                | Move selection up     |
| `Down` / `j`              | Move selection down   |
| `Shift+Up` / `Shift+Down` | Fast scroll (5 items) |
| `Enter`                   | Select current item   |

## Events

### Item selected

Fires when the user presses Enter on an option:

```typescript
import { SelectRenderableEvents, type SelectOption } from "@opentui/core"

menu.on(SelectRenderableEvents.ITEM_SELECTED, (index: number, option: SelectOption) => {
  console.log(`Selected index ${index}: ${option.name}`)
})
```

### Selection update

Fires after each movement attempt and each valid `setSelectedIndex()` call:

```typescript
menu.on(SelectRenderableEvents.SELECTION_CHANGED, (index: number, option: SelectOption | null) => {
  console.log(`Highlighted: ${option?.name ?? "none"}`)
  // Update a preview pane, for example
})
```

`SELECTION_CHANGED` can pass `null` as the option when the list is empty. `ITEM_SELECTED` does not fire without an option.
The event can fire when the index does not change. This occurs at a movement boundary or when you assign the current
index.

## Option structure

```typescript
interface SelectOption {
  name: string // Display text
  description: string // Displays below the name when showDescription is true
  value?: any // Optional value
}
```

## Styling

```typescript
const styledMenu = new SelectRenderable(renderer, {
  id: "styled-menu",
  width: 40,
  height: 10,
  options: [...],
  backgroundColor: "#1a1a1a",
  selectedBackgroundColor: "#333366",
  selectedTextColor: "#FFFFFF",
  textColor: "#AAAAAA",
  descriptionColor: "#666666",
})
```

## Properties

| Property                   | Type                                   | Default       | Description                          |
| -------------------------- | -------------------------------------- | ------------- | ------------------------------------ |
| `width`                    | number, `"auto"`, or percentage string | -             | Component width                      |
| `height`                   | number, `"auto"`, or percentage string | -             | Component height                     |
| `options`                  | `SelectOption[]`                       | `[]`          | Available options                    |
| `selectedIndex`            | `number`                               | `0`           | Initially selected index             |
| `backgroundColor`          | `string \| RGBA`                       | `transparent` | Background color                     |
| `textColor`                | `string \| RGBA`                       | `#FFFFFF`     | Normal text color                    |
| `focusedBackgroundColor`   | `string \| RGBA`                       | `#1a1a1a`     | Background when focused              |
| `focusedTextColor`         | `string \| RGBA`                       | `#FFFFFF`     | Text color when focused              |
| `selectedBackgroundColor`  | `string \| RGBA`                       | `#334455`     | Selected item background             |
| `selectedTextColor`        | `string \| RGBA`                       | `#FFFF00`     | Selected item text color             |
| `descriptionColor`         | `string \| RGBA`                       | `#888888`     | Description text color               |
| `selectedDescriptionColor` | `string \| RGBA`                       | `#CCCCCC`     | Selected item description color      |
| `showDescription`          | `boolean`                              | `true`        | Show option descriptions             |
| `showScrollIndicator`      | `boolean`                              | `false`       | Show scroll position indicator       |
| `showSelectionIndicator`   | `boolean`                              | `true`        | Show the selection marker and gutter |
| `wrapSelection`            | `boolean`                              | `false`       | Wrap selection at list boundaries    |
| `itemSpacing`              | `number`                               | `0`           | Spacing between items                |
| `fastScrollStep`           | `number`                               | `5`           | Items to skip with Shift+Up/Down     |

## Example: file menu

```typescript
import { BoxRenderable, SelectRenderable, createCliRenderer } from "@opentui/core"

const renderer = await createCliRenderer()

const fileMenu = new SelectRenderable(renderer, {
  width: 25,
  height: 12,
  options: [
    { name: "New", description: "Create new file (Ctrl+N)" },
    { name: "Open...", description: "Open file (Ctrl+O)" },
    { name: "Save", description: "Save file (Ctrl+S)" },
    { name: "Save As...", description: "Save with new name" },
    { name: "---", description: "" }, // Separator (visual only)
    { name: "Exit", description: "Quit application (Ctrl+Q)" },
  ],
})

const menuPanel = new BoxRenderable(renderer, {
  borderStyle: "single",
  borderColor: "#666",
})
menuPanel.add(fileMenu)

fileMenu.focus()
renderer.root.add(menuPanel)
```

## Programmatic control

```typescript
// Get current selection index
const currentIndex = menu.getSelectedIndex()

// Get currently selected option
const option = menu.getSelectedOption()

// Set selection programmatically
menu.setSelectedIndex(2)

// Navigate programmatically
menu.moveUp() // Move up one item
menu.moveDown() // Move down one item
menu.moveUp(3) // Move up multiple items
menu.selectCurrent() // Trigger selection of current item

// Update options dynamically
menu.options = [
  { name: "New Option 1", description: "First" },
  { name: "New Option 2", description: "Second" },
]

// Toggle display options
menu.showDescription = false
menu.showScrollIndicator = true
menu.showSelectionIndicator = false
menu.wrapSelection = true
```

## Related concepts

Read [Interaction, focus, and selection](/docs/core-concepts/interaction) for focus, keyboard, mouse, and event behavior. Use [ScrollBox](/docs/components/scrollbox) when you need to scroll arbitrary child content instead of options managed by Select.

---

--- METRICS ---
- Files Extracted: 45
- Estimated Token Budget: ~126365 tokens
- Canonical Reference: https://codewiki.google/github.com/anomalyco/opentui
