Skip to content

Terminal & Frame Reference

The Terminal and Frame classes form the heart of every pyratatui application. Terminal owns the screen, drives the render loop, and bridges keyboard events. Frame is passed to your draw callback each tick and provides all rendering methods.


Terminal

from pyratatui import Terminal

Terminal wraps ratatui's CrosstermBackend and manages alternate-screen mode, raw input, and the draw cycle.

Constructor

Terminal()

Creates a new Terminal instance. The screen is not initialized until __enter__ is called (via with Terminal() as t:).

Context Manager

with Terminal() as term:
    ...

__enter__: 1. Enables raw terminal mode (enable_raw_mode) 2. Enters alternate screen (EnterAlternateScreen) 3. Initializes the crossterm backend

__exit__: 1. Disables raw mode 2. Leaves alternate screen 3. Suppresses False (does not swallow exceptions)

Always Use as Context Manager

Never call Terminal() without a with statement. If your code raises an exception, the context manager guarantees terminal restoration. Without it, your user's terminal may be left in raw mode with the alternate screen active.

draw(draw_fn)

term.draw(lambda frame: ...)

Renders one frame by calling draw_fn(frame). The frame object is only valid inside the callback — do not store references to it.

Parameter Type Description
draw_fn Callable[[Frame], None] Function that renders widgets onto the frame

Raises: BackendError if the terminal is not initialized or the flush fails.

poll_event(timeout_ms=0)

ev = term.poll_event(timeout_ms=100)

Polls for a keyboard event.

Parameter Type Default Description
timeout_ms int 0 Milliseconds to wait (0 = non-blocking)

Returns: KeyEvent if a key was pressed during the timeout, otherwise None.

Only KeyEventKind::Press events are returned — key-repeat and release events are filtered out.

area()

rect = term.area()

Returns: Rect — the current terminal dimensions (updated on resize).

Raises: RuntimeError if the terminal is not initialized.

clear()

Forces a full repaint on the next draw() call. Use after a terminal resize event or when switching between large UI states.

hide_cursor() / show_cursor()

term.hide_cursor()
# ... rendering loop ...
term.show_cursor()

Hides or shows the terminal cursor. Always call show_cursor() before exiting so the user's cursor is restored.

restore()

term.restore()

Manually restore the terminal (disable raw mode, leave alternate screen). Called automatically by __exit__, but available for emergency cleanup.

Async Context Manager

async with Terminal() as term:
    ...

__aenter__ and __aexit__ provide the same behavior as the sync versions but in an async context. For typical async apps, prefer AsyncTerminal which handles frame pacing automatically.


AsyncTerminal

from pyratatui import AsyncTerminal

An asyncio-compatible wrapper around Terminal. All Terminal calls happen on the asyncio event-loop thread — never via thread-pool executors.

Async Context Manager

async with AsyncTerminal() as term:
    ...

Equivalent to entering and exiting Terminal synchronously on the main async thread.

events(fps=30.0, *, stop_on_quit=False)

By default events() keeps yielding each frame until your code breaks the loop; pass stop_on_quit=True to opt into auto-stopping when q or Ctrl+C are pressed.

async for ev in term.events(fps=30):
    term.draw(ui)
    if ev:
        handle(ev)

Async generator that drives the render loop:

  1. Non-blocking poll_event(0) on the main thread
  2. Yields KeyEvent | None
  3. await asyncio.sleep(remaining_frame_time) — yields to background coroutines
Parameter Type Default Description
fps float 30.0 Target frames per second
stop_on_quit bool False Auto-stop on q or Ctrl+C when enabled

draw(draw_fn) / poll_event() / area() / clear() / hide_cursor() / show_cursor()

Same signatures as Terminal — these are thin wrappers that assert the terminal is active before delegating.


Frame

# Frame is never constructed directly — it arrives as the argument to draw_fn:
def ui(frame: Frame):
    ...

Frame is only valid inside the draw_fn callback. Storing a reference outside the callback and using it later is undefined behavior.

area (property)

area: Rect = frame.area

The full terminal area available for this frame. Equivalent to frame.size.

size (property)

Alias for area.

render_widget(widget, area)

frame.render_widget(widget, area)

Render a stateless widget into area.

Supported widget types: Block, Paragraph, Gauge, LineGauge, BarChart, Sparkline, Clear, Tabs, Monthly, BarGraph, Canvas, Map, Button, Throbber, PieChart, Checkbox, Chart, List (stateless), Table (stateless).

Parameter Type Description
widget widget object Any supported widget instance
area Rect Where to draw the widget

Raises: RenderError if the widget type is not recognized.

render_stateful_list(widget, area, state)

frame.render_stateful_list(list_widget, area, list_state)

Render a List with mutable selection state. The state object is mutated in-place (scroll offset is updated to keep the selection visible).

Parameter Type Description
widget List The list widget
area Rect Target area
state ListState Mutable selection state

render_stateful_table(widget, area, state)

frame.render_stateful_table(table_widget, area, table_state)

Render a Table with mutable selection state.

Parameter Type Description
widget Table The table widget
area Rect Target area
state TableState Mutable selection state

render_stateful_scrollbar(widget, area, state)

frame.render_stateful_scrollbar(scrollbar, area, scrollbar_state)

Render a Scrollbar with its scroll position state.

render_stateful_menu(widget, area, state)

frame.render_stateful_menu(menu, area, menu_state)

Render a Menu with mutable MenuState.

apply_effect(effect, elapsed_ms, area)

frame.apply_effect(effect, elapsed_ms, area)

Apply a single TachyonFX Effect to the frame buffer. Call after all render_widget calls.

Parameter Type Description
effect Effect The effect to advance and apply
elapsed_ms int Wall-clock milliseconds since last frame
area Rect Area of buffer cells to transform

apply_effect_manager(manager, elapsed_ms, area)

frame.apply_effect_manager(mgr, elapsed_ms, area)

Advance all effects in mgr and remove completed ones. Call after all render_widget calls.

Parameter Type Description
manager EffectManager The effect manager to advance
elapsed_ms int Wall-clock ms since last frame
area Rect Area to apply effects to

KeyEvent

from pyratatui import KeyEvent

Returned by Terminal.poll_event() and AsyncTerminal.poll_event().

Properties

Property Type Description
code str Key code string (see table below)
ctrl bool Whether Ctrl was held
alt bool Whether Alt was held
shift bool Whether Shift was held

Key Code Strings

Physical key code value
Letter az "a""z"
Letter AZ "A""Z"
Digits 09 "0""9"
Enter / Return "Enter"
Escape "Esc"
Backspace "Backspace"
Delete "Delete"
Tab "Tab"
Shift+Tab "BackTab"
Arrow Up "Up"
Arrow Down "Down"
Arrow Left "Left"
Arrow Right "Right"
Home "Home"
End "End"
Page Up "PageUp"
Page Down "PageDown"
Insert "Insert"
F1–F12 "F1""F12"
Space " "
Unknown "Unknown"

Example: Key Handling

ev = term.poll_event(timeout_ms=100)
if ev:
    if ev.code == "q":
        running = False
    elif ev.code == "c" and ev.ctrl:
        running = False
    elif ev.code == "Up":
        state.select_previous()
    elif ev.code == "Down":
        state.select_next()
    elif ev.code == "Enter":
        confirm_selection()
    elif ev.code == "F5":
        refresh()

Exception Hierarchy

All pyratatui exceptions derive from PyratatuiError:

from pyratatui import (
    PyratatuiError,   # Base class
    BackendError,     # Terminal I/O failures
    LayoutError,      # Invalid layout constraints
    RenderError,      # Unknown widget type
    AsyncError,       # Async bridging errors
    StyleError,       # Invalid style values
)

try:
    with Terminal() as term:
        term.draw(ui)
except BackendError as e:
    print(f"Terminal error: {e}")
except PyratatuiError as e:
    print(f"pyratatui error: {e}")

Helper Functions

run_app(draw_fn, fps=30)

from pyratatui import run_app

def my_ui(frame):
    frame.render_widget(Paragraph.from_string("Hello!"), frame.area)

run_app(my_ui, fps=30)

Synchronous convenience wrapper: creates a Terminal, runs the draw loop at fps, and depends on your on_key callback returning True to exit; the helper no longer interprets q or Ctrl+C for you.

run_app_async(draw_fn, fps=30)

from pyratatui import run_app_async
import asyncio

async def my_ui(frame):
    frame.render_widget(Paragraph.from_string("Async!"), frame.area)

asyncio.run(run_app_async(my_ui, fps=30))

Async version using AsyncTerminal. Provide an on_key callback to dictate when the helper should stop.