๐๐ PyRatatui โ Professional Python bindings for ratatui 0.30 ๐ฆ๐คฉ | ๐ฅ๐ฅ High-performance Rust TUI engine for building rich terminal applications, interactive CLIs, dashboards, and text-based UIs ๐ฏโจ
Project description
๐ PyRatatui
Professional Python bindings for ratatui 0.30 โ powered by Rust & PyO3
Build rich, high-performance terminal UIs in Python โ with the full power of Rust under the hood.
Quickstart ยท Installation ยท Widgets ยท Effects ยท Examples ยท API Reference ยท Docs
๐ผ๏ธ Gallery
What is PyRatatui?
PyRatatui exposes the entire ratatui Rust TUI library to Python via a thin, zero-overhead PyO3 extension module. You get:
- Pixel-perfect terminal rendering from ratatui's battle-tested Rust layout engine
- 35+ widgets out of the box: gauges, tables, trees, menus, charts, calendars, QR codes, images, markdown, and more
- TachyonFX animations โ fade, sweep, glitch, dissolve, and composable effect pipelines
- Async-native โ
AsyncTerminal+asynciointegration for live, reactive UIs - Full type stubs โ every class and method ships with
.pyiannotations for IDE autocomplete - Cross-platform โ Linux, macOS, and Windows (pre-built wheels on PyPI for all three)
Table of Contents
- Installation
- Quickstart
- Core Concepts
- Widget Reference
- TachyonFX Effects
- Async & Reactive UIs
- CLI Tool
- API Reference
- Examples
- Building from Source
- Contributing
- License
Installation
Recommended โ Pre-built Wheel
pip install pyratatui
Pre-built wheels are published to PyPI for:
- Linux x86_64 (manylinux2014)
- Linux x86_64 and aarch64 (musllinux_1_2) (starting from v0.2.3)
- macOS x86_64 (starting from v0.2.2) and arm64 (universal2)
- Windows x86_64
If no wheel exists for your platform, pip will automatically compile from source (requires Rust โ see Building from Source).
Virtual Environment (Best Practice)
python -m venv .venv
source .venv/bin/activate # Linux / macOS
# .venv\Scripts\activate # Windows PowerShell
pip install pyratatui
Requirements
| Requirement | Minimum | Notes |
|---|---|---|
| Python | 3.10 | 3.11+ recommended |
| OS | Linux, macOS, Windows | crossterm backend |
| Rust | 1.75 | source builds only |
Verify
import pyratatui
print(pyratatui.__version__) # "0.2.7"
print(pyratatui.__ratatui_version__) # "0.30"
Quickstart
Hello World
from pyratatui import Block, Color, Paragraph, Style, Terminal
with Terminal() as term:
while True:
def ui(frame):
frame.render_widget(
Paragraph.from_string("Hello, pyratatui! ๐ Press q to quit.")
.block(Block().bordered().title("Hello World"))
.style(Style().fg(Color.cyan())),
frame.area,
)
term.draw(ui)
ev = term.poll_event(timeout_ms=100)
if ev and ev.code == "q":
break
Output:
โ Hello World โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Hello, pyratatui! ๐ Press q to quit. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Scaffold a New Project
pyratatui init my_app
cd my_app
pip install -r requirements.txt
python main.py
Ultra-Minimal โ run_app Helper
from pyratatui import Paragraph, run_app
def ui(frame):
frame.render_widget(
Paragraph.from_string("Hello! Press q to quit."),
frame.area,
)
run_app(ui)
Core Concepts
Terminal & Frame
Terminal is the entry point. Use it as a context manager โ it saves the terminal state, enters alternate screen mode, enables raw input, and restores everything on exit (even after exceptions).
frame is not a global variable and you never construct it yourself. Each call to term.draw(...), AsyncTerminal.draw(...), run_app(...), or run_app_async(...) creates a temporary Frame for that render pass and passes it into your callback. Use it only inside that callback.
with Terminal() as term:
term.draw(lambda frame: ...) # pyratatui creates frame and passes it in
ev = term.poll_event(timeout_ms=50) # KeyEvent | None
Frame holds the drawable area and all render methods for the current pass:
def ui(frame):
area = frame.area # Rect โ full terminal size
frame.render_widget(widget, area)
Layout
Layout divides a Rect into child regions using constraints:
from pyratatui import (
Block,
Constraint,
Direction,
Layout,
Paragraph,
run_app,
)
def ui(frame):
header, body, footer = (
Layout()
.direction(Direction.Vertical)
.constraints([
Constraint.length(3), # fixed 3 rows
Constraint.fill(1), # takes remaining space
Constraint.length(1), # fixed 1 row
])
.split(frame.area)
)
frame.render_widget(Block().bordered().title("Header"), header)
frame.render_widget(
Paragraph.from_string("Main content").block(Block().bordered().title("Body")),
body,
)
frame.render_widget(Paragraph.from_string("Press q to quit"), footer)
run_app(ui)
Constraint types:
| Constraint | Description |
|---|---|
Constraint.length(n) |
Exactly n rows/columns |
Constraint.percentage(pct) |
pct% of available space |
Constraint.fill(n) |
Fill remaining space (proportionally weighted) |
Constraint.min(n) |
At least n rows/columns |
Constraint.max(n) |
At most n rows/columns |
Constraint.ratio(num, den) |
Fractional proportion |
Styling
All styling flows through Style, Color, and Modifier:
from pyratatui import Style, Color, Modifier
style = (
Style()
.fg(Color.cyan())
.bg(Color.rgb(30, 30, 46))
.bold()
.italic()
)
# Named colors
Color.red() Color.green() Color.yellow()
Color.blue() Color.magenta() Color.cyan()
Color.white() Color.gray() Color.dark_gray()
# Light variants: Color.light_red(), Color.light_green(), ...
# 256-color: Color.indexed(42)
# True-color: Color.rgb(255, 100, 0)
Text Hierarchy
Text is composed bottom-up: Span โ Line โ Text:
from pyratatui import Block, Color, Line, Paragraph, Span, Style, Text, run_app
def ui(frame):
text = Text([
Line([
Span("Status: ", Style().bold()),
Span("OK", Style().fg(Color.green())),
Span(" | 99.9%", Style().fg(Color.cyan())),
]),
Line.from_string("Plain text line"),
Line.from_string("Right-aligned").right_aligned(),
])
frame.render_widget(
Paragraph(text).block(Block().bordered().title("Text Hierarchy")),
frame.area,
)
run_app(ui)
Key Events
ev = term.poll_event(timeout_ms=100)
if ev:
print(ev.code) # "q", "Enter", "Up", "Down", "F1", etc.
print(ev.ctrl) # True if Ctrl held
print(ev.alt) # True if Alt held
print(ev.shift) # True if Shift held
# Common key codes
# Letters/digits: "a", "Z", "5"
# Special: "Enter", "Esc", "Backspace", "Tab", "BackTab"
# Arrows: "Up", "Down", "Left", "Right"
# Function: "F1" โฆ "F12"
# Ctrl+C: ev.code == "c" and ev.ctrl
Tip โ Closure Capture: Always snapshot mutable state into default arguments to avoid late-binding issues in fast render loops:
count = state["count"] def ui(frame, _count=count): # โ captured by value, not reference ...
Widget Reference
Standard Widgets
| Widget | Description |
|---|---|
Paragraph |
Single or multi-line text, wrapping, scrolling |
Block |
Bordered container with title, padding, and style |
List + ListState |
Scrollable, selectable list |
Table + TableState |
Multi-column table with header and footer |
Gauge |
Filled progress bar |
LineGauge |
Single-line progress indicator |
BarChart |
Grouped vertical bar chart |
Sparkline |
Inline sparkline trend chart |
Scrollbar + ScrollbarState |
Attach scrollbars to any widget |
Tabs |
Tabbed navigation bar |
Clear |
Clears a rectangular area (use under popups) |
RatatuiMascot |
Ratatui mascot widget (easter egg) |
Runnable widget gallery:
from pyratatui import (
Block,
Color,
Constraint,
Direction,
Gauge,
Layout,
List,
ListItem,
ListState,
Row,
Sparkline,
Style,
Table,
TableState,
Tabs,
run_app,
)
list_state = ListState()
list_state.select(0)
table_state = TableState()
table_state.select(0)
def ui(frame, _list_state=list_state, _table_state=table_state):
rows = (
Layout()
.direction(Direction.Vertical)
.constraints([
Constraint.length(3),
Constraint.length(3),
Constraint.fill(1),
Constraint.length(5),
])
.split(frame.area)
)
middle = (
Layout()
.direction(Direction.Horizontal)
.constraints([Constraint.percentage(40), Constraint.fill(1)])
.split(rows[2])
)
frame.render_widget(
Tabs(["Overview", "Logs", "Config"])
.select(1)
.block(Block().bordered().title("Tabs"))
.highlight_style(Style().fg(Color.yellow()).bold()),
rows[0],
)
frame.render_widget(
Gauge()
.percent(75)
.label("CPU 75%")
.style(Style().fg(Color.green()))
.block(Block().bordered().title("Gauge")),
rows[1],
)
items = [ListItem(s) for s in ["Alpha", "Beta", "Gamma"]]
frame.render_stateful_list(
List(items)
.block(Block().bordered().title("List"))
.highlight_style(Style().fg(Color.yellow()).bold())
.highlight_symbol("โถ "),
middle[0],
_list_state,
)
header = Row.from_strings(["Name", "Status", "Uptime"]).style(
Style().fg(Color.cyan()).bold()
)
table_rows = [
Row.from_strings(["nginx", "running", "14d"]),
Row.from_strings(["postgres", "running", "21d"]),
Row.from_strings(["redis", "degraded", "3h"]),
]
frame.render_stateful_table(
Table(
table_rows,
[Constraint.fill(1), Constraint.length(10), Constraint.length(8)],
header=header,
)
.block(Block().bordered().title("Table"))
.highlight_style(Style().fg(Color.yellow()).bold())
.highlight_symbol("โถ "),
middle[1],
_table_state,
)
frame.render_widget(
Sparkline()
.data([10, 40, 20, 80, 55, 90])
.max(100)
.style(Style().fg(Color.cyan()))
.block(Block().bordered().title("Sparkline")),
rows[3],
)
run_app(ui)
Third-Party Widgets
| Widget | Crate | Description |
|---|---|---|
Popup / PopupState |
tui-popup |
Centered or draggable popups |
TextArea |
tui-textarea |
Full multi-line editor (Emacs keybindings, undo/redo) |
ScrollView / ScrollViewState |
tui-scrollview |
Scrollable virtual viewport |
QrCodeWidget |
tui-qrcode |
QR codes rendered in Unicode block characters |
Monthly / CalendarDate |
ratatui widget-calendar |
Monthly calendar with event styling |
BarGraph |
tui-bar-graph |
Gradient braille/block bar graphs |
Tree / TreeState |
tui-tree-widget |
Collapsible tree view |
TuiLoggerWidget |
tui-logger |
Live scrolling log viewer |
ImageWidget / ImagePicker |
ratatui-image |
Terminal image rendering |
Canvas |
ratatui |
Low-level line/point/rect drawing |
Map |
ratatui |
World map widget |
Button |
built-in | Focus-aware interactive button |
Throbber |
throbber-widgets-tui |
Animated spinner/progress indicator |
Menu / MenuState |
tui-menu |
Nested dropdown menus with event handling |
PieChart / PieData / PieStyle |
tui-piechart |
Pie chart widget with legend and percentages |
Checkbox |
tui-checkbox |
Configurable checkbox widget |
Chart / Dataset / Axis |
ratatui |
Multi-dataset cartesian chart (line/scatter/bar) |
Image Rendering
ImageWidget supports Kitty, Sixel, iTerm2 inline images, and a Unicode
half-block fallback. For best clarity, call ImagePicker.from_query() inside
Terminal() to auto-detect the best protocol and cell size, and the renderer
uses a high-quality Lanczos3 resampling filter when resizing.
Third-party widget gallery:
from pyratatui import (
BarColorMode,
BarGraph,
BarGraphStyle,
Block,
CalendarDate,
CalendarEventStore,
Color,
Constraint,
Direction,
Layout,
Monthly,
Paragraph,
Popup,
PopupState,
QrCodeWidget,
QrColors,
Style,
TextArea,
Tree,
TreeItem,
TreeState,
markdown_to_text,
run_app,
)
popup = Popup("Press q to dismiss").title("Popup").style(Style().bg(Color.blue()))
popup_state = PopupState()
textarea = TextArea.from_lines(["Hello", "World"])
textarea.set_block(Block().bordered().title("TextArea"))
tree = Tree([
TreeItem("src", [TreeItem("main.rs"), TreeItem("lib.rs")]),
TreeItem("Cargo.toml"),
]).block(Block().bordered().title("Tree"))
tree_state = TreeState()
tree_state.select([0])
def ui(frame, _popup_state=popup_state, _ta=textarea, _tree=tree, _tree_state=tree_state):
rows = (
Layout()
.direction(Direction.Vertical)
.constraints([
Constraint.length(12),
Constraint.length(10),
Constraint.fill(1),
])
.split(frame.area)
)
top = (
Layout()
.direction(Direction.Horizontal)
.constraints([
Constraint.percentage(25),
Constraint.percentage(25),
Constraint.percentage(25),
Constraint.fill(1),
])
.split(rows[0])
)
middle = (
Layout()
.direction(Direction.Horizontal)
.constraints([Constraint.fill(1), Constraint.length(28)])
.split(rows[1])
)
qr_block = Block().bordered().title("QR Code")
frame.render_widget(qr_block, top[0])
frame.render_qrcode(
QrCodeWidget("https://ratatui.rs").colors(QrColors.Inverted),
qr_block.inner(top[0]),
)
store = CalendarEventStore.today_highlighted(Style().fg(Color.green()).bold())
frame.render_widget(
Monthly(CalendarDate.today(), store)
.block(Block().bordered().title("Calendar"))
.show_month_header(Style().bold())
.show_weekdays_header(Style().italic()),
top[1],
)
graph_block = Block().bordered().title("Bar Graph")
frame.render_widget(graph_block, top[2])
frame.render_widget(
BarGraph([0.1, 0.4, 0.9, 0.6, 0.8])
.bar_style(BarGraphStyle.Braille)
.color_mode(BarColorMode.VerticalGradient)
.gradient("turbo"),
graph_block.inner(top[2]),
)
frame.render_stateful_popup(popup, top[3], _popup_state)
frame.render_widget(
Paragraph(markdown_to_text("# Hello\n\n**bold** _italic_ `code`"))
.block(Block().bordered().title("Markdown")),
middle[0],
)
frame.render_stateful_tree(_tree, middle[1], _tree_state)
frame.render_textarea(_ta, rows[2])
run_app(ui)
TachyonFX Effects
PyRatatui ships the full tachyonfx effects engine. Effects are post-render transforms that mutate the frame buffer โ always apply them after rendering your widgets.
Effect Types
| Effect | Description |
|---|---|
Effect.fade_from_fg(color, ms) |
Fade text from a color into its natural color |
Effect.fade_to_fg(color, ms) |
Fade text out to a flat color |
Effect.fade_from(bg, fg, ms) |
Fade both background and foreground from color |
Effect.fade_to(bg, fg, ms) |
Fade both background and foreground to color |
Effect.coalesce(ms) |
Characters materialize in from random positions |
Effect.dissolve(ms) |
Characters scatter and dissolve |
Effect.slide_in(direction, ms) |
Slide content in from an edge |
Effect.slide_out(direction, ms) |
Slide content out to an edge |
Effect.sweep_in(dir, span, grad, color, ms) |
Gradient sweep reveal |
Effect.sweep_out(dir, span, grad, color, ms) |
Gradient sweep hide |
Effect.sequence(effects) |
Run effects one after another |
Effect.parallel(effects) |
Run effects simultaneously |
Effect.sleep(ms) |
Delay before next effect in a sequence |
Effect.repeat(effect, times=-1) |
Loop an effect (โ1 = forever) |
Effect.ping_pong(effect) |
Play an effect forward then backward |
Effect.never_complete(effect) |
Keep an effect alive indefinitely |
Interpolations
Interpolation.Linear, QuadIn/Out/InOut, CubicIn/Out/InOut, SineIn/Out/InOut,
CircIn/Out/InOut, ExpoIn/Out/InOut, ElasticIn/Out, BounceIn/Out/BounceInOut, BackIn/Out/BackInOut
Basic Effect Usage
import time
from pyratatui import Effect, EffectManager, Interpolation, Color, Terminal, Paragraph
mgr = EffectManager()
mgr.add(Effect.fade_from_fg(Color.black(), 1000, Interpolation.SineOut))
last = time.monotonic()
with Terminal() as term:
while not (ev := term.poll_event(timeout_ms=16)) or ev.code != "q":
now = time.monotonic()
elapsed_ms = int((now - last) * 1000)
last = now
def ui(frame, _mgr=mgr, _ms=elapsed_ms):
# Step 1 โ render widgets
frame.render_widget(Paragraph.from_string("Fading inโฆ"), frame.area)
# Step 2 โ apply effects to the same buffer
frame.apply_effect_manager(_mgr, _ms, frame.area)
term.draw(ui)
Effect DSL
Compile tachyonfx expressions at runtime โ perfect for config-driven or user-customisable animations:
from pyratatui import compile_effect, EffectManager
# DSL mirrors the Rust / tachyonfx expression syntax
effect = compile_effect("fx::coalesce(500)")
effect = compile_effect("fx::dissolve((800, BounceOut))")
effect = compile_effect("fx::fade_from_fg(Color::Black, (600, QuadOut))")
effect = compile_effect("fx::sweep_in(LeftToRight, 10, 5, Color::Black, (700, SineOut))")
mgr = EffectManager()
mgr.add(effect)
Cell Filters
Target effects at specific cells:
from pyratatui import CellFilter, Effect, Color
effect = Effect.fade_from_fg(Color.black(), 800)
effect.with_filter(CellFilter.text()) # text cells only
effect.with_filter(CellFilter.inner(horizontal=1, vertical=1)) # inner area
effect.with_filter(CellFilter.fg_color(Color.cyan())) # specific fg color
effect.with_filter(CellFilter.any_of([CellFilter.text(), CellFilter.all()]))
Async & Reactive UIs
Use AsyncTerminal to combine rendering with background asyncio tasks:
import asyncio
from pyratatui import AsyncTerminal, Gauge, Block, Style, Color
state = {"progress": 0}
async def background_worker():
while state["progress"] < 100:
await asyncio.sleep(0.1)
state["progress"] += 2
async def main():
worker = asyncio.create_task(background_worker())
async with AsyncTerminal() as term:
async for ev in term.events(fps=30):
pct = state["progress"]
def ui(frame, _pct=pct):
frame.render_widget(
Gauge()
.percent(_pct)
.label(f"Loadingโฆ {_pct}%")
.style(Style().fg(Color.green()))
.block(Block().bordered().title("Progress")),
frame.area,
)
term.draw(ui)
if ev and ev.code == "q":
break
if pct >= 100:
break
worker.cancel()
asyncio.run(main())
AsyncTerminal.events() Parameters
By default events() keeps yielding each tick; pass stop_on_quit=True to opt into automatic exit on q/Ctrl+C.
async for ev in term.events(fps=30.0, stop_on_quit=True):
# ev is KeyEvent | None
# None emitted each tick (use for animations / periodic updates)
# stop_on_quit=True (opt-in) exits the loop automatically on "q" or Ctrl+C
run_app / run_app_async Helpers
For simpler apps that don't need manual task management; keep in mind that quitting must be implemented via on_key or another explicit signal.
from pyratatui import run_app, run_app_async, Paragraph
# Synchronous
def ui(frame):
frame.render_widget(
Paragraph.from_string("Hello!"),
frame.area
)
run_app(ui, on_key=lambda ev: ev.code == "q")
# Asynchronous
import asyncio
async def main():
tick = 0
def ui(frame):
nonlocal tick
frame.render_widget(Paragraph.from_string(f"Tick: {tick}"), frame.area)
tick += 1
await run_app_async(ui, fps=30, on_key=lambda ev: ev.code == "q")
asyncio.run(main())
CLI Tool
PyRatatui ships a pyratatui CLI for project scaffolding and version inspection.
Usage: pyratatui [COMMAND]
Commands:
init Create a new PyRatatui project scaffold
version Show PyRatatui version
Options:
--help Show help message
pyratatui init
pyratatui init my_tui_app [--verbose]
Creates a ready-to-run project:
my_tui_app/
โโโ main.py # runnable hello world starter
โโโ pyproject.toml # app metdata
โโโ .gitignore # skip unnecessary files from commit
โโโ README.md # project docs
cd my_tui_app
pip install -r requirements.txt
python main.py
pyratatui version
pyratatui version
# PyRatatui 0.2.7
API Reference
Terminal
class Terminal:
def __enter__(self) -> Terminal
def __exit__(self, ...) -> bool
def draw(self, draw_fn: Callable[[Frame], None]) -> None
def poll_event(self, timeout_ms: int = 0) -> KeyEvent | None
def area(self) -> Rect
def clear(self) -> None
def hide_cursor(self) -> None
def show_cursor(self) -> None
def restore(self) -> None
AsyncTerminal
class AsyncTerminal:
async def __aenter__(self) -> AsyncTerminal
async def __aexit__(self, ...) -> bool
def draw(self, draw_fn: Callable[[Frame], None]) -> None
async def poll_event(self, timeout_ms: int = 50) -> KeyEvent | None
async def events(self, fps: float = 30.0, *, stop_on_quit: bool = False) -> AsyncIterator[KeyEvent | None]
def area(self) -> Rect
def clear(self) -> None
def hide_cursor(self) -> None
def show_cursor(self) -> None
Frame
class Frame:
@property
def area(self) -> Rect
# Standard widgets (stateless)
def render_widget(self, widget: object, area: Rect) -> None
# Stateful widgets
def render_stateful_list(self, widget: List, area: Rect, state: ListState) -> None
def render_stateful_table(self, widget: Table, area: Rect, state: TableState) -> None
def render_stateful_scrollbar(self, widget: Scrollbar, area: Rect, state: ScrollbarState) -> None
def render_stateful_menu(self, widget: Menu, area: Rect, state: MenuState) -> None
# Popups
def render_popup(self, popup: Popup, area: Rect) -> None
def render_stateful_popup(self, popup: Popup, area: Rect, state: PopupState) -> None
# Text editor
def render_textarea(self, ta: TextArea, area: Rect) -> None
# Scroll view
def render_stateful_scrollview(self, sv: ScrollView, area: Rect, state: ScrollViewState) -> None
# QR code
def render_qrcode(self, qr: QrCodeWidget, area: Rect) -> None
# Effects
def apply_effect(self, effect: Effect, elapsed_ms: int, area: Rect) -> None
def apply_effect_manager(self, manager: EffectManager, elapsed_ms: int, area: Rect) -> None
# Prompts
def render_text_prompt(self, prompt: TextPrompt, area: Rect, state: TextState) -> None
def render_password_prompt(self, prompt: PasswordPrompt, area: Rect, state: TextState) -> None
Layout & Geometry
class Layout:
def constraints(self, constraints: list[Constraint]) -> Layout
def direction(self, direction: Direction) -> Layout
def margin(self, margin: int) -> Layout
def spacing(self, spacing: int) -> Layout
def flex_mode(self, mode: str) -> Layout
def split(self, area: Rect) -> list[Rect]
class Rect:
x: int; y: int; width: int; height: int
right: int; bottom: int; left: int; top: int
def area(self) -> int
def inner(self, horizontal: int = 1, vertical: int = 1) -> Rect
def contains(self, other: Rect) -> bool
def intersection(self, other: Rect) -> Rect | None
def union(self, other: Rect) -> Rect
Style
class Style:
def fg(self, color: Color) -> Style
def bg(self, color: Color) -> Style
def bold(self) -> Style
def italic(self) -> Style
def underlined(self) -> Style
def dim(self) -> Style
def reversed(self) -> Style
def hidden(self) -> Style
def crossed_out(self) -> Style
def slow_blink(self) -> Style
def rapid_blink(self) -> Style
def patch(self, other: Style) -> Style
def add_modifier(self, modifier: Modifier) -> Style
def remove_modifier(self, modifier: Modifier) -> Style
Block
class Block:
def title(self, title: str) -> Block
def title_bottom(self, title: str) -> Block
def bordered(self) -> Block # all four borders
def borders(self, top, right, bottom, left) -> Block
def border_type(self, bt: BorderType) -> Block # Plain | Rounded | Double | Thick
def style(self, style: Style) -> Block
def border_style(self, style: Style) -> Block
def title_style(self, style: Style) -> Block
def padding(self, left, right, top, bottom) -> Block
def title_alignment(self, alignment: str) -> Block
Prompts
from pyratatui import (
Terminal,
TextPrompt,
TextState,
prompt_password,
prompt_text,
)
# Blocking single-line text prompt (runs its own event loop)
value: str | None = prompt_text("Enter your name: ")
password: str | None = prompt_password("Password: ")
# Stateful inline prompts
state = TextState()
state.focus()
with Terminal() as term:
term.hide_cursor()
while state.is_pending():
def ui(frame, _state=state):
frame.render_text_prompt(TextPrompt("Search: "), frame.area, _state)
term.draw(ui)
ev = term.poll_event(timeout_ms=50)
if ev:
state.handle_key(ev)
term.show_cursor()
if state.is_complete():
print(state.value())
elif state.is_aborted():
print("Prompt aborted.")
Exceptions
| Exception | When raised |
|---|---|
PyratatuiError |
Base exception for all library errors |
BackendError |
Terminal backend failure |
LayoutError |
Invalid layout constraint or split |
RenderError |
Widget render failure |
AsyncError |
Async / thread misuse |
StyleError |
Invalid style combination |
Examples
The examples/ directory contains 38 standalone, runnable scripts. Run any of them directly:
python examples/01_hello_world.py
python examples/07_async_reactive.py
python examples/08_effects_fade.py
OR run all of them:
python test_all_examples.py
| # | File | Demonstrates |
|---|---|---|
| 01 | 01_hello_world.py |
Terminal, Paragraph, Block, Style, Color |
| 02 | 02_layout.py |
Layout, Constraint, Direction, nested splits |
| 03 | 03_styled_text.py |
Span, Line, Text, Modifier |
| 04 | 04_list_navigation.py |
List, ListState, keyboard navigation |
| 05 | 05_progress_bar.py |
Gauge, LineGauge, time-based animation |
| 06 | 06_table_dynamic.py |
Table, Row, Cell, TableState |
| 07 | 07_async_reactive.py |
AsyncTerminal, live background metrics |
| 08 | 08_effects_fade.py |
Effect.fade_from_fg, EffectManager |
| 09 | 09_effects_dsl.py |
compile_effect(), DSL syntax |
| 10 | 10_full_app.py |
Full production app: tabs, async, effects |
| 11 | 11_popup_basic.py |
Popup โ basic centered popup |
| 12 | 12_popup_stateful.py |
PopupState โ draggable popup |
| 13 | 13_popup_scrollable.py |
KnownSizeWrapper โ scrollable popup content |
| 14 | 14_textarea_basic.py |
TextArea โ basic multi-line editor |
| 15 | 15_textarea_advanced.py |
TextArea โ modal vim-style editing |
| 16 | 16_scrollview.py |
ScrollView, ScrollViewState |
| 17 | 17_qrcode.py |
QrCodeWidget, QrColors |
| 18 | 18_async_progress.py |
Async live progress with asyncio.Task |
| 19 | 19_effects_glitch.py |
dissolve / coalesce glitch animation |
| 20 | 20_effects_matrix.py |
sweep_in / sweep_out matrix-style |
| 21 | 21_prompt_confirm.py |
Yes/No confirmation prompt |
| 22 | 22_prompt_select.py |
Arrow-key selection menu |
| 23 | 23_prompt_text.py |
TextPrompt, TextState |
| 24 | 24_dashboard.py |
Full dashboard: Tabs, BarChart, Sparkline |
| 25 | 25_calendar.py |
Monthly, CalendarDate, CalendarEventStore |
| 26 | 26_bar_graph.py |
BarGraph, gradient styles |
| 27 | 27_tree_widget.py |
Tree, TreeState, collapsible nodes |
| 28 | 28_markdown_renderer.py |
markdown_to_text() |
| 29 | 29_logger_demo.py |
TuiLoggerWidget, init_logger |
| 30 | 30_image_view.py |
ImagePicker, ImageWidget, ImageState |
| 31 | 31_canvas_drawing.py |
Canvas โ lines, points, rectangles |
| 32 | 32_map_widget.py |
Map, MapResolution |
| 33 | 33_button_widget.py |
Button โ focus state, key handling |
| 34 | 34_throbber.py |
Throbber โ start/stop and speed control |
| 35 | 35_menu_widget.py |
Menu, MenuState, MenuEvent |
| 36 | 36_piechart.py |
PieChart, PieData, PieStyle |
| 37 | 37_checkbox_widget.py |
Checkbox โ checked/unchecked toggle |
| 38 | 38_chart_widget.py |
Chart, Dataset, Axis, GraphType |
Building from Source
Prerequisites
# 1. Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
rustup update stable
# 2. Install Maturin
pip install maturin
Development Build
git clone https://github.com/pyratatui/pyratatui.git
cd pyratatui
# Editable install โ fast compile, slower runtime
maturin develop
# Release build โ full Rust optimizations (recommended for benchmarking/use)
maturin develop --release
After changing Rust source files, re-run maturin develop to rebuild the extension. Python files in python/pyratatui/ are reflected immediately with no rebuild.
Build a Distributable Wheel
maturin build --release
# Wheel output: target/wheels/pyratatui-*.whl
pip install target/wheels/pyratatui-*.whl
Format & Lint
# Linux / macOS
./scripts/format.sh
# Windows
./scripts/format.ps1
# Python only (ruff + mypy)
ruff check .
ruff format .
mypy python/
Tests
# Python tests (pytest)
pytest tests/python/
# Rust unit tests
cargo test
Docker (source build)
FROM python:3.12-slim
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"
RUN pip install pyratatui
Platform Notes
Windows
Requires Windows Terminal or VS Code integrated terminal (Windows 10 build 1903+ for VT sequence support). The classic cmd.exe may not render all Unicode characters correctly.
macOS
Default Terminal.app works but has limited colour support. iTerm2 or Alacritty are recommended for true-colour and full Unicode rendering.
Linux
Any modern terminal emulator works. Verify true-colour support:
echo $COLORTERM # should output "truecolor" or "24bit"
Troubleshooting
ModuleNotFoundError: No module named 'pyratatui._pyratatui'
The native extension was not compiled. Run maturin develop --release or reinstall via pip install --force-reinstall pyratatui.
PanicException: pyratatui::terminal::Terminal is unsendable
You called a Terminal method from a thread-pool thread. Use AsyncTerminal instead.
Garbage on screen after Ctrl-C
Always use Terminal as a context manager. For emergency recovery: reset or stty sane in your shell.
ValueError: Invalid date
CalendarDate.from_ymd(y, m, d) raises ValueError for invalid dates (e.g. Feb 30). Validate inputs first.
Contributing
Contributions are welcome! Here's how to get started:
- Fork the repository on GitHub
- Clone your fork and create a branch:
git checkout -b feature/my-feature - Install dev dependencies:
pip install -e ".[dev]" maturin develop
- Make your changes โ Rust source lives in
src/, Python inpython/pyratatui/ - Run tests and linters:
pytest tests/python/ cargo test ruff check . && ruff format . mypy python/
- Open a Pull Request against
main
Please follow the existing code style. For significant changes, open an issue first to discuss your approach.
Documentation
Docs are built with MkDocs Material:
pip install -e ".[docs]"
mkdocs serve # local preview at http://localhost:8000
mkdocs build # static site in site/
License
MIT ยฉ 2026 PyRatatui contributors โ see LICENSE for full text.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyratatui-0.2.8.tar.gz.
File metadata
- Download URL: pyratatui-0.2.8.tar.gz
- Upload date:
- Size: 1.1 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e712e8f35346dadc4884b36c8b2d4702ddeecdab6e72bd50e7268d91fa18cc6f
|
|
| MD5 |
bb44c4351fa7f76d570a51fe32fe83e8
|
|
| BLAKE2b-256 |
011859ead08f571b0a1ad19f4d5564fb1c0da5efd9b16787fdc7d393741f955d
|
Provenance
The following attestation bundles were made for pyratatui-0.2.8.tar.gz:
Publisher:
ci.yml on pyratatui/pyratatui
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyratatui-0.2.8.tar.gz -
Subject digest:
e712e8f35346dadc4884b36c8b2d4702ddeecdab6e72bd50e7268d91fa18cc6f - Sigstore transparency entry: 1262928681
- Sigstore integration time:
-
Permalink:
pyratatui/pyratatui@9aa9cd8d039e2924a1d91b7e355c02390f54ace9 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/pyratatui
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@9aa9cd8d039e2924a1d91b7e355c02390f54ace9 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file pyratatui-0.2.8-cp310-abi3-win_amd64.whl.
File metadata
- Download URL: pyratatui-0.2.8-cp310-abi3-win_amd64.whl
- Upload date:
- Size: 2.9 MB
- Tags: CPython 3.10+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
09e65009fde7ded14279fba8d654f630bab98ec6a792d84ee5d4dceb4c197369
|
|
| MD5 |
3c6e40f25b65ae4b0689e3da65ac7d37
|
|
| BLAKE2b-256 |
3ea8d41736da31551503e6a2421210191a441221f6303b21bbd4bc98e14620f1
|
Provenance
The following attestation bundles were made for pyratatui-0.2.8-cp310-abi3-win_amd64.whl:
Publisher:
ci.yml on pyratatui/pyratatui
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyratatui-0.2.8-cp310-abi3-win_amd64.whl -
Subject digest:
09e65009fde7ded14279fba8d654f630bab98ec6a792d84ee5d4dceb4c197369 - Sigstore transparency entry: 1262928721
- Sigstore integration time:
-
Permalink:
pyratatui/pyratatui@9aa9cd8d039e2924a1d91b7e355c02390f54ace9 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/pyratatui
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@9aa9cd8d039e2924a1d91b7e355c02390f54ace9 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file pyratatui-0.2.8-cp310-abi3-musllinux_1_2_x86_64.whl.
File metadata
- Download URL: pyratatui-0.2.8-cp310-abi3-musllinux_1_2_x86_64.whl
- Upload date:
- Size: 3.0 MB
- Tags: CPython 3.10+, musllinux: musl 1.2+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
680f1db32b3b7f6c6e92087144d26a4cd3a2005b11ec4c40816de4429ca4cb78
|
|
| MD5 |
4aaa0f3372a26a7bad3d4d9856ef8854
|
|
| BLAKE2b-256 |
7720c3cfcf7a011b1de2c6c62930aad2cae9e4e279679e2ceedbc64ed0ec48bf
|
Provenance
The following attestation bundles were made for pyratatui-0.2.8-cp310-abi3-musllinux_1_2_x86_64.whl:
Publisher:
ci.yml on pyratatui/pyratatui
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyratatui-0.2.8-cp310-abi3-musllinux_1_2_x86_64.whl -
Subject digest:
680f1db32b3b7f6c6e92087144d26a4cd3a2005b11ec4c40816de4429ca4cb78 - Sigstore transparency entry: 1262928691
- Sigstore integration time:
-
Permalink:
pyratatui/pyratatui@9aa9cd8d039e2924a1d91b7e355c02390f54ace9 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/pyratatui
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@9aa9cd8d039e2924a1d91b7e355c02390f54ace9 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file pyratatui-0.2.8-cp310-abi3-musllinux_1_2_aarch64.whl.
File metadata
- Download URL: pyratatui-0.2.8-cp310-abi3-musllinux_1_2_aarch64.whl
- Upload date:
- Size: 2.9 MB
- Tags: CPython 3.10+, musllinux: musl 1.2+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2b3bbde545708cb0e5e73af2891b4debdb0450a842cc9a394d427adb1236eac1
|
|
| MD5 |
c2cbea1267b850ba13ba6292fece8184
|
|
| BLAKE2b-256 |
155940f1f78cd74f60bcc250cf6b77c52c9f368784c51a22fb40d7fb29a40ac6
|
Provenance
The following attestation bundles were made for pyratatui-0.2.8-cp310-abi3-musllinux_1_2_aarch64.whl:
Publisher:
ci.yml on pyratatui/pyratatui
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyratatui-0.2.8-cp310-abi3-musllinux_1_2_aarch64.whl -
Subject digest:
2b3bbde545708cb0e5e73af2891b4debdb0450a842cc9a394d427adb1236eac1 - Sigstore transparency entry: 1262928725
- Sigstore integration time:
-
Permalink:
pyratatui/pyratatui@9aa9cd8d039e2924a1d91b7e355c02390f54ace9 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/pyratatui
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@9aa9cd8d039e2924a1d91b7e355c02390f54ace9 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file pyratatui-0.2.8-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: pyratatui-0.2.8-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 2.8 MB
- Tags: CPython 3.10+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3036073e2213864d7f7a3e1d94bba3b365601f76aedc8652c6787622665d0111
|
|
| MD5 |
c951b99662236ab0a19276f2105cc946
|
|
| BLAKE2b-256 |
ab0990eb896313a8cb546a4ab7bb346d634a0927c80bef4748dd74744fbd58c5
|
Provenance
The following attestation bundles were made for pyratatui-0.2.8-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
ci.yml on pyratatui/pyratatui
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyratatui-0.2.8-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
3036073e2213864d7f7a3e1d94bba3b365601f76aedc8652c6787622665d0111 - Sigstore transparency entry: 1262928712
- Sigstore integration time:
-
Permalink:
pyratatui/pyratatui@9aa9cd8d039e2924a1d91b7e355c02390f54ace9 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/pyratatui
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@9aa9cd8d039e2924a1d91b7e355c02390f54ace9 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file pyratatui-0.2.8-cp310-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: pyratatui-0.2.8-cp310-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 2.6 MB
- Tags: CPython 3.10+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
48d61acb562e7424e25e99cfdf38b2d04b488744c0307f882877e32fa7bc59cc
|
|
| MD5 |
0713fce97301858f73f1e5bbb7e67823
|
|
| BLAKE2b-256 |
d80bed835d726cd0a140b9f769297b0a959b3474cf85c3cc4502f19e15b03cbf
|
Provenance
The following attestation bundles were made for pyratatui-0.2.8-cp310-abi3-macosx_11_0_arm64.whl:
Publisher:
ci.yml on pyratatui/pyratatui
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyratatui-0.2.8-cp310-abi3-macosx_11_0_arm64.whl -
Subject digest:
48d61acb562e7424e25e99cfdf38b2d04b488744c0307f882877e32fa7bc59cc - Sigstore transparency entry: 1262928716
- Sigstore integration time:
-
Permalink:
pyratatui/pyratatui@9aa9cd8d039e2924a1d91b7e355c02390f54ace9 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/pyratatui
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@9aa9cd8d039e2924a1d91b7e355c02390f54ace9 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file pyratatui-0.2.8-cp310-abi3-macosx_10_12_x86_64.whl.
File metadata
- Download URL: pyratatui-0.2.8-cp310-abi3-macosx_10_12_x86_64.whl
- Upload date:
- Size: 2.7 MB
- Tags: CPython 3.10+, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0c8abc11f15de51de8445ba68a4df791684ddf7d056c622e950316bdc3931a20
|
|
| MD5 |
9ab91b597a94fd5b232694b4dba391f9
|
|
| BLAKE2b-256 |
138f6beefaead7ef7de47bef7bf814b8bee7cbf51030871d11a2313a2eb65f39
|
Provenance
The following attestation bundles were made for pyratatui-0.2.8-cp310-abi3-macosx_10_12_x86_64.whl:
Publisher:
ci.yml on pyratatui/pyratatui
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyratatui-0.2.8-cp310-abi3-macosx_10_12_x86_64.whl -
Subject digest:
0c8abc11f15de51de8445ba68a4df791684ddf7d056c622e950316bdc3931a20 - Sigstore transparency entry: 1262928701
- Sigstore integration time:
-
Permalink:
pyratatui/pyratatui@9aa9cd8d039e2924a1d91b7e355c02390f54ace9 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/pyratatui
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@9aa9cd8d039e2924a1d91b7e355c02390f54ace9 -
Trigger Event:
workflow_dispatch
-
Statement type: