Skip to main content

Python bindings for OpenTUI - Build terminal UIs in Python

Project description

OpenTUI Python

PyPI version Python License: MIT

A Pythonic port of OpenTUI — build rich terminal UIs in Python with reactive signals, flexbox layout, and a full component library.

OpenTUI is the rendering engine behind OpenCode. This package brings that same engine to Python: the native Zig core handles rendering and layout, while the Python layer provides an idiomatic API with signals, components, hooks, and async rendering.

Disclaimer: This is an independent community project. It is not affiliated with, endorsed by, or connected to OpenTUI, OpenCode, or Anomaly in any way. OpenTUI is developed by the anomalyco/opentui team and is used here under its MIT license.

Features

  • Reactive signals — fine-grained reactivity with Signal, computed, effect, and Expr operators
  • Flexbox layout — powered by Yoga via yoga-python
  • Rich componentsBox, Text, Input, Textarea, Select, ScrollBox, Markdown, Code, Diff, and more
  • Native performance — Zig core with nanobind C++ bindings for rendering-critical paths
  • Full input handling — keyboard, mouse, and paste events
  • Image support — Kitty and SIXEL graphics protocols
  • Syntax highlighting — Tree-sitter integration for code blocks
  • 4,700+ tests — comprehensive parity with the OpenTUI core test suite

Installation

pip install opentui

With optional extras:

pip install opentui[images]        # Pillow for image support
pip install opentui[highlighting]  # Tree-sitter syntax highlighting
pip install opentui[dev]           # pytest, ruff, ty

Quick Start

import asyncio
from opentui import render, Box, Text, Signal, component, use_keyboard, use_renderer

count = Signal(0, name="count")

@component
def App():
    return Box(
        Text(lambda: f"Count: {count()}"),
        Text("Press +/- to change, q to quit"),
        padding=2, border=True, gap=1,
    )

def on_key(event):
    if event.name == "q":
        use_renderer().stop()
    elif event.name in ("+", "="):
        count.add(1)
    elif event.name == "-":
        count.add(-1)

async def main():
    use_keyboard(on_key)
    await render(App)

asyncio.run(main())

Reactive Patterns

OpenTUI provides three tiers of reactivity — choose the simplest one that fits:

Direct signal prop — pass a Signal directly to any supported prop for zero-overhead updates:

color = Signal("red", name="color")
Text("Hello", fg=color)  # updates paint when color changes

Lambda / callable — use a lambda for computed or derived values:

count = Signal(0, name="count")
Text(lambda: f"Count: {count()}")

.map() transform — transform a signal's value without a full lambda:

Text(count.map(lambda v: f"Count: {v}"))

Expr operators — signals support arithmetic and comparison operators that return reactive expressions:

doubled = count * 2              # Expr: evaluates to count() * 2
is_high = count > 5              # Expr: evaluates to count() > 5
label = count.if_("yes", "no")   # Conditional: "yes" if truthy, "no" otherwise

Batch updates — group multiple signal writes into a single notification pass:

from opentui import Batch

with Batch():
    x.set(1)
    y.set(2)  # subscribers only fire once, after the block

Control Flow

Conditional and list rendering with Show, Switch, Match, and For:

from opentui import Show, Switch, Match, For, Signal

visible = Signal(True, name="visible")
mode = Signal("home", name="mode")
items = Signal(["a", "b", "c"], name="items")

# Conditional rendering
Show(Text("Visible!"), when=visible)
Show(Text("Visible!"), when=visible, fallback=Text("Hidden"))

# Multi-branch conditional
Switch(
    Match(HomePage(), when=mode.map(lambda m: m == "home")),
    Match(Settings(), when=mode.map(lambda m: m == "settings")),
    fallback=Text("Not found"),
)

# Signal-keyed switch (fast path — no re-subscription on change)
Switch(on=mode, cases={
    "home": HomePage(),
    "settings": Settings(),
})

# List rendering
For(lambda item, i: Text(f"{i}: {item}"), each=items)

Components

Use @component to define reusable components. Each invocation gets its own reactive scope:

from opentui import component, Signal, Box, Text

@component
def Counter(label: str = "Count"):
    count = Signal(0, name="count")
    return Box(
        Text(count.map(lambda v: f"{label}: {v}")),
        border=True,
    )

For lower-level control, use Mount directly:

from opentui import Mount, Signal, Text

counter = Mount(lambda: Text(Signal(0, name="n").map(str)))

Layout

Component Description
Box Flexbox container with border, padding, background
ScrollBox Scrollable container with mouse wheel support

Text

Component Description
Text Styled text with wrapping, selection, and inline modifiers
Bold, Italic, Underline Inline text style modifiers
Span Colored inline text spans
Link Clickable terminal hyperlinks

Input

Component Description
Input Single-line text input
Textarea Multi-line editor with native buffer, undo/redo, and syntax highlighting
Select Dropdown selection list

Advanced

Component Description
Code Syntax-highlighted code block via Tree-sitter
Diff Side-by-side and unified diff viewer
Markdown Rendered markdown with headings, lists, tables, code blocks
LineNumberRenderable Line number gutter (pairs with Code or Textarea)
Slider Numeric value slider
TabSelect Tab selection bar
TextTable Tabular text layout with borders

Control Flow

Component Description
For Keyed list rendering with efficient reconciliation
Show Conditional rendering
Switch / Match Multi-branch conditional rendering
Lazy Deferred child construction (built on first render)
Portal Render children into a different mount point
Dynamic / MemoBlock Dynamic node selection and memoized subtrees

Signals

from opentui import Signal, computed, effect

name = Signal("world", name="name")
greeting = computed(lambda: f"Hello, {name()}!")

effect(lambda: print(greeting()))  # prints "Hello, world!"
name.set("Python")                 # prints "Hello, Python!"

Hooks

from opentui import use_keyboard, use_mouse, use_paste, use_on_resize, use_timeline

use_keyboard(lambda event: print(event.name))
use_mouse(lambda event: print(event.type, event.x, event.y))
use_paste(lambda event: print(event.text))
use_on_resize(lambda cols, rows: print(f"{cols}x{rows}"))

timeline = use_timeline()
timeline.add(target, {"opacity": 1.0}, duration=300)

Development

git clone https://github.com/banditburai/opentui-python.git
cd opentui-python
uv sync --all-extras

# Run tests
uv run pytest tests/ -v

# Lint & type checking
uv run ruff check
uv run ruff format --check
uv run ty check

Test coverage

The test suite includes 4,700+ tests: a comprehensive 1:1 port of the OpenTUI core test suite plus Python-specific tests for signals, FFI bindings, and the reconciler.

Architecture

The package is a hybrid: the OpenTUI Zig core handles rendering, text buffers, and layout at native speed, while the Python layer implements the component model, signals runtime, reconciler, and public API. Performance-critical paths are additionally accelerated with nanobind C++ extensions.

OpenTUI Core (Zig) → libopentui.so/dylib
    ↓
nanobind C++ bindings (opentui_bindings)
    ↓
Python API (opentui)
    ├── signals       — Signal, computed, effect, Expr operators
    ├── components/   — Box, Text, Input, ScrollBox, Code, Diff, ...
    ├── hooks         — use_keyboard, use_mouse, use_paste, use_on_resize
    ├── renderer      — CliRenderer, Buffer, TerminalCapabilities
    ├── reconciler    — Component tree diffing (idiomorph-inspired)
    └── layout        — Yoga flexbox integration via yoga-python

What's native vs. Python:

  • Native (Zig via nanobind): text buffers, edit buffers, editor views, hit testing, graphics encoding, buffer rendering, syntax styling
  • C++ extensions: signal→prop bindings, reconciler patching, render tree dispatch
  • Python: signals runtime, component tree, event loop, input parsing, all public API

Pre-built wheels are provided for Linux (x86_64, aarch64), macOS (x86_64, arm64), and Windows (x64) on Python 3.12+.

License

MIT — see LICENSE.

OpenTUI core is also MIT licensed. yoga-python is MIT licensed.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

opentui-0.1.0.tar.gz (796.9 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

opentui-0.1.0-cp313-cp313-manylinux_2_28_x86_64.whl (3.0 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.28+ x86-64

opentui-0.1.0-cp313-cp313-manylinux_2_28_aarch64.whl (2.9 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.28+ ARM64

opentui-0.1.0-cp313-cp313-macosx_13_0_x86_64.whl (777.5 kB view details)

Uploaded CPython 3.13macOS 13.0+ x86-64

opentui-0.1.0-cp313-cp313-macosx_13_0_arm64.whl (752.2 kB view details)

Uploaded CPython 3.13macOS 13.0+ ARM64

opentui-0.1.0-cp312-cp312-manylinux_2_28_x86_64.whl (3.0 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.28+ x86-64

opentui-0.1.0-cp312-cp312-manylinux_2_28_aarch64.whl (2.9 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.28+ ARM64

opentui-0.1.0-cp312-cp312-macosx_13_0_x86_64.whl (777.6 kB view details)

Uploaded CPython 3.12macOS 13.0+ x86-64

opentui-0.1.0-cp312-cp312-macosx_13_0_arm64.whl (752.3 kB view details)

Uploaded CPython 3.12macOS 13.0+ ARM64

File details

Details for the file opentui-0.1.0.tar.gz.

File metadata

  • Download URL: opentui-0.1.0.tar.gz
  • Upload date:
  • Size: 796.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for opentui-0.1.0.tar.gz
Algorithm Hash digest
SHA256 e598c2d8e6f151795c1f150f66867a94d3fd70fad7bd3a62d7cc524cf2139d83
MD5 396f708d0ff48b3c97b2cbec6d54416f
BLAKE2b-256 388502ae69edaf1625cf4b5afdd1d316158e371cb98992cb3d07f15cdc182a45

See more details on using hashes here.

Provenance

The following attestation bundles were made for opentui-0.1.0.tar.gz:

Publisher: release.yml on banditburai/opentui-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file opentui-0.1.0-cp313-cp313-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for opentui-0.1.0-cp313-cp313-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 381319f27cf3f562bbce6b28243da0af26f387a8232febf24da0a544b2571ab9
MD5 5b63b9c9de37be4f5e2ab74656352077
BLAKE2b-256 c102eff318d7849089e017feb88d6f61ff880c7cfe0d60a4b8ca27a8b6912e24

See more details on using hashes here.

Provenance

The following attestation bundles were made for opentui-0.1.0-cp313-cp313-manylinux_2_28_x86_64.whl:

Publisher: release.yml on banditburai/opentui-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file opentui-0.1.0-cp313-cp313-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for opentui-0.1.0-cp313-cp313-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 f54aa12f403ff708ae53a07f2608070097398b8e8590e99306c00c81e641f893
MD5 760cf5ba03e1017493064d44f1b13725
BLAKE2b-256 166d048f19269363902500c9decfe8335182e3e8a765975571830324bc6347d4

See more details on using hashes here.

Provenance

The following attestation bundles were made for opentui-0.1.0-cp313-cp313-manylinux_2_28_aarch64.whl:

Publisher: release.yml on banditburai/opentui-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file opentui-0.1.0-cp313-cp313-macosx_13_0_x86_64.whl.

File metadata

File hashes

Hashes for opentui-0.1.0-cp313-cp313-macosx_13_0_x86_64.whl
Algorithm Hash digest
SHA256 b31d9c0b3e6250dc96d5d73f74021c8874611d2363d990c8cb128cacb3bf1687
MD5 e6714a7489080faae230f32307347f91
BLAKE2b-256 2be140e7f3e3d23d9bf3832ffc922f81c0bb080ce4bb09e8b1569d786f50400f

See more details on using hashes here.

Provenance

The following attestation bundles were made for opentui-0.1.0-cp313-cp313-macosx_13_0_x86_64.whl:

Publisher: release.yml on banditburai/opentui-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file opentui-0.1.0-cp313-cp313-macosx_13_0_arm64.whl.

File metadata

File hashes

Hashes for opentui-0.1.0-cp313-cp313-macosx_13_0_arm64.whl
Algorithm Hash digest
SHA256 e2d489efbe7359eb0b36887e0f3fe3b863bfae73142fef01df29cd79cc17c545
MD5 d024babaa3f8e4f4cc06a36fbd380f92
BLAKE2b-256 ef4baa9cd45dc27d38bd16368564b5696dfc6ff77ccc5fe887315009f5b74b0e

See more details on using hashes here.

Provenance

The following attestation bundles were made for opentui-0.1.0-cp313-cp313-macosx_13_0_arm64.whl:

Publisher: release.yml on banditburai/opentui-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file opentui-0.1.0-cp312-cp312-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for opentui-0.1.0-cp312-cp312-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 bc93658f595b1c3e71149f90a9e70b3a46194dea5ad740397ff73c13a0784ba2
MD5 9da45bd94c4b1b639b5afa01a72ad86b
BLAKE2b-256 d7dfae7825f846dccc9a4d4928bd7ac28e47fcb16b023f9ac279268011192cd6

See more details on using hashes here.

Provenance

The following attestation bundles were made for opentui-0.1.0-cp312-cp312-manylinux_2_28_x86_64.whl:

Publisher: release.yml on banditburai/opentui-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file opentui-0.1.0-cp312-cp312-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for opentui-0.1.0-cp312-cp312-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 4866a15fe0547979404258d810cd3700f8126ca20aaf1c8cd5effdeb646ca61c
MD5 919aa417c0cfd6ee0e51500a883e06c1
BLAKE2b-256 ee9ac93b8650168961278f4a9a25e5b3d59dd1c68f01d8a67c1af249ae7aefa2

See more details on using hashes here.

Provenance

The following attestation bundles were made for opentui-0.1.0-cp312-cp312-manylinux_2_28_aarch64.whl:

Publisher: release.yml on banditburai/opentui-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file opentui-0.1.0-cp312-cp312-macosx_13_0_x86_64.whl.

File metadata

File hashes

Hashes for opentui-0.1.0-cp312-cp312-macosx_13_0_x86_64.whl
Algorithm Hash digest
SHA256 2c97e4ca2a7ef6ae068a8faeb3f823549b8aa70ea1a23d8539b6afa32afd3bcb
MD5 88d426b0d95acaa99859cb814ddce38a
BLAKE2b-256 ff8848ccf5d6b8f99b844eb9cad1fe948e9144819ae575db4d54c61cdfdbb2d0

See more details on using hashes here.

Provenance

The following attestation bundles were made for opentui-0.1.0-cp312-cp312-macosx_13_0_x86_64.whl:

Publisher: release.yml on banditburai/opentui-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file opentui-0.1.0-cp312-cp312-macosx_13_0_arm64.whl.

File metadata

File hashes

Hashes for opentui-0.1.0-cp312-cp312-macosx_13_0_arm64.whl
Algorithm Hash digest
SHA256 904588870cce608a91306ec2c977a4950edbebbb27709656a0a52ab2a58e5cd2
MD5 821829ef6ec3a3678b76ccdcec2dbc8c
BLAKE2b-256 9a3cc70274ea1d0c1765d81078f744e613b25f4580dbe8b5e8a176a509fa59e8

See more details on using hashes here.

Provenance

The following attestation bundles were made for opentui-0.1.0-cp312-cp312-macosx_13_0_arm64.whl:

Publisher: release.yml on banditburai/opentui-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page