package

github.com/threez/tui.cr

0.5.0 / published Aug 5, 2026 / repository

Minimal Crystal TUI primitives.

tui.cr

Minimal Crystal TUI primitives: a diffing screen compositor, a handful of composable widgets (lists, tables, detail views, scrollable panes, splits, modals), and an app-shell (Runtime) that owns terminal lifecycle and the render/read-key/dispatch loop.

See doc/architecture.md for the layering overview, which links out to doc/rendering.md (the render/input pipelines), doc/widgets.md (the full widget catalog), doc/app-shell.md (NavStack/ Runtime), and doc/dsl.md (the macro-based DSL layer).

Installation

dependencies:
  tui:
    path: threez/tui.cr

Hello, world

Every app builds its own Widget subclass, wraps it in a NavStack, and hands both to Runtime, which owns the terminal lifecycle and the render/read-key/dispatch loop:

require "tui"

class Hello < TUI::Widget
  def render : Nil
    @buffer.set(0, 0, "Hello, World! (press q to quit)")
  end

  def handle_key(ev : TUI::KeyEvent) : Bool
    false # unconsumed
  end

  def status_hint : String
    "q:quit"
  end
end

screen = TUI::Screen.new
nav = TUI::NavStack(TUI::Widget).new(Hello.new(1, 1, screen.cols, screen.rows).as(TUI::Widget))
runtime = TUI::Runtime.new(screen, nav, ->(ev : TUI::KeyEvent) {
  exit if ev.key == TUI::Key::Char && ev.char == 'q'
})
runtime.run

Run it in a real terminal — Runtime takes over the whole screen (alt screen, raw mode, mouse reporting) until the process exits.

Example app

example/ contains a larger "widget browser" exercising most of the library's widgets end to end against fabricated in-memory data. Run it with:

make example
# or: crystal run example/widget_browser.cr

Testing

crystal spec
crystal build --no-codegen src/tui.cr # fast compile-only check

API