Getting started

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.

Installation

OpenTUI’s renderer examples currently use Bun. Published portable entrypoints can be imported from Node.js without Bun or FFI flags, including @opentui/keymap and import-only @opentui/core usage. Creating a native renderer from Node is a separate runtime path and currently targets Node 26.x with experimental FFI enabled.

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

Runtime support

Import-only package use does not require native FFI. For example, await import("@opentui/keymap") and await import("@opentui/core") can load in Node without --experimental-ffi.

Renderer support is different. Calling createCliRenderer() or APIs that load and call OpenTUI’s native Zig renderer needs native FFI. Under Node, that path currently requires Node 26.3.0 with --experimental-ffi and, when using Node permissions, --allow-ffi plus the filesystem permissions your app needs. OpenTUI does not install Node; select the required version before running Node-specific examples or scripts.

Hello world

Create index.ts:

import { createCliRenderer, Text } from "@opentui/core"

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

renderer.root.add(
  Text({
    content: "Hello, OpenTUI!",
    fg: "#00FF00",
  }),
)

Run it:

bun index.ts

You should see green text. Press Ctrl+C to exit.

Composing components

Components nest naturally. Here’s a bordered panel with content:

import { createCliRenderer, Box, Text } from "@opentui/core"

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

renderer.root.add(
  Box(
    { borderStyle: "rounded", padding: 1, flexDirection: "column", gap: 1 },
    Text({ content: "Welcome", fg: "#FFFF00" }),
    Text({ content: "Press Ctrl+C to exit" }),
  ),
)

Box and Text are factory functions. The first argument is props; additional arguments are children.

What’s next

Core concepts

Components

Framework bindings