> OpenTUI [upstream](https://github.com/anomalyco/opentui) + experimental patchset for Node.js >=22 from [github.com/justjake/opentui](https://github.com/justjake/opentui) by [@jitl](https://twitter.com/jitl) ([diff](https://github.com/anomalyco/opentui/compare/main...justjake:opentui:4610ae795bb71cdbb464b6346567c92a27fd0492)).
>
> - Support Node.js v22 (upstream requires Node.js v26's `node:ffi`).
> - Support React 18 (upstream requires React 19).
> - Support `require(ESM)` by removing top-level `await` expressions (upstream requires `await import(...)` in CommonJS modules).
> - `await writeReactToScrollback(renderer, <View />)` helper for split-footer mode.
> - `CliRenderEvents.RAW_MOUSE` and React `useRawMouse(event => ...)` expose parsed terminal mouse events relative to the screen rather than the renderer's coordinate system.
> - `clearOnShutdown: false` preserves visible main-screen/split-footer content and scrollback on shutdown.
> - `footerHeight: "auto"` resolves split-footer height from the committed Yoga/Flexbox layout tree, clamped by `minFooterHeight` and `maxFooterHeight`.
> - Changes to `split-footer` screen mode:
>
> ```typescript
> /**
>  * Choose whether writes to `stderr.write` join captured external output to
>  * stdout.
>  *
>  * - "auto": capture stderr only when external output capture is active and
>  *    stderr appears to write to stdout's destination.
>  * - "always": capture process.stderr whenever external output capture is active.
>  * - "never": leave stderr alone. (Upstream @opntui/core behavior)
>  * - WriteStream: capture the provided stream whenever external output capture
>  *   is active.
>  *
>  * Defaults to "auto".
>  */
> externalOutputCaptureStderr?: ExternalOutputCaptureStderr
>
> /**
>  * Choose how captured external output is rendered in split-footer mode.
>  *
>  * - "emulated": render captured output through OpenTUI layout engine. ANSI
>  *   color/style sequences are currently unhandled and produce rendering
>  *   artifacts (Upstream @opntui/core behavior).
>  * - "terminal-native": write captured output unmodified to its destination,
>  *   while accounting for its layout. This preserves terminal-native ANSI
>  *   behavior and wrapping, but some ANSI sequences may produce unexpected
>  *   behavior.
>  *
>  * Defaults to "terminal-native".
>  */
> externalOutputRendering?: ExternalOutputRendering
> ```
# OpenTUI Core

OpenTUI is a native terminal UI core written in Zig with TypeScript bindings. The native core exposes a C ABI and can be used from any language. OpenTUI powers OpenCode in production today and will also power terminal.shop. It is an extensible core with a focus on correctness, stability, and high performance. It provides a component-based architecture with flexible layout capabilities, allowing you to create complex terminal applications.

## Documentation

- [Getting Started](docs/getting-started.md) - API and usage guide
- [Development Guide](docs/development.md) - Building, testing, and contributing
- [Tree-Sitter](docs/tree-sitter.md) - Syntax highlighting integration
- [Renderables vs Constructs](docs/renderables-vs-constructs.md) - Understanding the component model
- [Environment Variables](docs/env-vars.md) - Configuration options

## Install

```bash
bun install @jitl/opentui-core
```

## Build

```bash
bun run build
```

This creates platform-specific libraries that are automatically loaded by the TypeScript layer.

## Examples

```bash
bun install
cd ../examples
bun run dev
```

## Benchmarks

Run native performance benchmarks:

```bash
bun run bench:native
```

See [src/zig/bench.zig](src/zig/bench.zig) for available options like `--filter` and `--mem`.

NativeSpanFeed TypeScript benchmarks:

- [src/benchmark/native-span-feed-benchmark.md](src/benchmark/native-span-feed-benchmark.md)

## CLI Renderer

### Renderables

Renderables are hierarchical objects that can be positioned, nested, styled and rendered to the terminal:

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

const renderer = await createCliRenderer()

const obj = new TextRenderable(renderer, { id: "my-obj", content: "Hello, world!" })

renderer.root.add(obj)
```
