LmCast :: Stay tuned in

Show HN: RatatuiRuby wraps Rust Ratatui as a RubyGem – TUIs with the joy of Ruby

Recorded: Jan. 22, 2026, 11:03 a.m.

Original Summarized

RatatuiRuby — Terminal UIs in Ruby

RatatuiRuby

Better CLIs
Full TUIs
Frameworks
API →
Guides →
Examples →

Terminal UIs,the Ruby way.
RatatuiRuby is a RubyGem built
on Ratatui, a leading
TUI library written in Rust. You get native performance with the joy of Ruby.

$ gem install ratatui_ruby --pre
SemVer Tag: v1.0.0-beta.2 RubyGems.org: 1.0.0.pre.beta.2

Start small
Rich Moments
Add a spinner, a progress bar, or an inline menu to your CLI script. No full-screen
takeover. Your terminal history stays intact.

Inline Viewports
Standard TUIs erase themselves on exit. Your carefully formatted CLI output disappears. Users lose their
scrollback.
Inline viewports solve this. They occupy a fixed number of lines, render rich UI, then
leave the output in place when done.
Perfect for spinners, menus, progress indicators—any brief moment of richness.

class Spinner
def main
RatatuiRuby.run(viewport: :inline, height: 1) do |tui|
until connected?
status = tui.paragraph(text: "#{spin} Connecting...")
tui.draw { |frame| frame.render_widget(status, frame.area) }
return ending(tui, "Canceled!", :red) if tui.poll_event.ctrl_c?
end
ending(tui, "Connected!", :green)
end
end

def ending(tui, message, color) = tui.draw do |frame|
frame.render_widget(tui.paragraph(text: message, fg: color), frame.area)
end

def initialize = (@frame, @finish = 0, Time.now + 2)
def connected? = Time.now >= @finish # Simulate work
def spin = SPINNER[(@frame += 1) % SPINNER.length]
SPINNER = %w[⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏]
end
Spinner.new.main; puts

See more: Inline menu example

require "ratatui_ruby"

# This example renders an inline menu. Arrow keys select, enter confirms.
# The menu appears in-place, preserving scrollback. When the user chooses,
# the TUI closes and the script continues with the selected value.
class RadioMenu
CHOICES = ["Production", "Staging", "Development"] # ASCII strings are universally supported.
PREFIXES = { active: "●", inactive: "○" } # Some terminals may not support Unicode.
CONTROLS = "↑/↓: Select | Enter: Choose | Ctrl+C: Cancel"# Let users know what keys you handle.
TITLES = ["Select Environment", # The default title position is top left.
{ content: CONTROLS, # Multiple titles can save space.
position: :bottom, # Titles go on the top or bottom,
alignment: :right }] # aligned left, right, or center

def call # This method blocks until a choice is made.
RatatuiRuby.run(viewport: :inline, height: 5) do |tui| # RatauiRuby.run manages the terminal.
@tui = tui # The TUI instance is safe to store.
show_menu until chosen? # You can use any loop keyword you like.
end # `run` won't return until your block does,
RadioMenu::CHOICES[@choice] # so you can use it synchronously.
end
# Classes like RadioMenu are convenient for
private # CLI authors to offer "rich moments."

def show_menu = @tui.draw do |frame| # RatatuiRuby gives you low-level access.
widget = @tui.paragraph( # But the TUI facade makes it easy to use.
text: menu_items, # Text can be spans, lines, or paragraphs.
block: @tui.block(borders: :all, titles: TITLES) # Blocks give you boxes and titles, and hold
) # one or more widgets. We only use one here,
frame.render_widget(widget, frame.area) # but "area" lets you compose sub-views.
end

def chosen? # You are responsible for handling input.
interaction = @tui.poll_event # Every frame, you receive an event object:
return choose if interaction.enter? # Key, Mouse, Resize, Paste, FocusGained,
# FocusLost, or None objects. They come with
move_by(-1) if interaction.up? # predicates, support pattern matching, and
move_by(1) if interaction.down? # can be inspected for properties directly.
quit! if interaction.ctrl_c? # Your application must handle every input,
false # even interrupts and other exit patterns.
end

def choose # Here, the loop is about to exit, and the
prepare_next_line # block will return. The inline viewport
@choice # will be torn down and the terminal will
end # be restored, but you are responsible for
# positioning the cursor.
def prepare_next_line # To ensure the next output is on a new
area = @tui.viewport_area # line, query the viewport area and move
RatatuiRuby.cursor_position = [0, area.y + area.height] # the cursor to the start of the last line.
puts # Then print a newline.
end

def quit! # All of your familiar Ruby control flow
prepare_next_line # keywords work as expected, so we can
exit 0 # use them to leave the TUI.
end

def move_by(line_count) # You are in full control of your UX, so
@choice = (@choice + line_count) % CHOICES.size # you can implement any logic you need:
end # Would you "wrap around" here, or not?
#
def menu_items = CHOICES.map.with_index do |choice, i| # Notably, RatatuiRuby has no concept of
"#{prefix_for(i)} #{choice}" # "menus" or "radio buttons". You are in
end # full control, but it also means you must
def prefix_for(choice_index) # implement the logic yourself. For larger
return PREFIXES[:active] if choice_index == @choice # applications, consider using Rooibos,
PREFIXES[:inactive] # an MVU framework built with RatatuiRuby.
end # Or, use the upcoming ratatui-ruby-kit,
# our object-oriented component library.
def initialize = @choice = 0 # However, those are both optional, and
end # designed for full-screen Terminal UIs.
# RatatuiRuby will always give you the most
choice = RadioMenu.new.call # control, and is enough for "rich CLI
puts "You chose #{choice}!" # moments" like this one.

Go further
Build Something Real
Full-screen applications with keyboard and mouse input.
The managed loop sets up the terminal and restores it on exit, even after crashes.

RatatuiRuby.run do |tui|
loop do
tui.draw do |frame|
frame.render_widget(
tui.paragraph(
text: "Hello, RatatuiRuby!",
alignment: :center,
block: tui.block(
title: "My App",
titles: [{ content: "q: Quit", position: :bottom, alignment: :right }],
borders: [:all],
border_style: { fg: "cyan" }
)
),
frame.area
)
end

case tui.poll_event
in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
break
else
nil
end
end
end

The Managed Loop
RatatuiRuby.run enters raw mode, switches to the alternate screen, and restores the terminal
on exit.
Inside the block: call draw to render, and poll_event to read input.
Widgets included:

Bar Chart
Block
Calendar
Canvas
Clear (Popup, Modal)
Cell
Center
Chart
Gauge
Layout (Split, Grid)
Line Gauge
List
Map (World Map)
Overlay
Rich Text (Line, Span)
Scrollbar (Scroll)
Sparkline
Table
Tabs

Need something else? Build
custom widgets in Ruby!

Ship with confidence
Testing Built In
TUI testing is tedious. You need a headless terminal, event injection, snapshot
comparisons, and style assertions. RatatuiRuby bundles all of it.

require "ratatui_ruby/test_helper"

class TestColorPicker < Minitest::Test
include RatatuiRuby::TestHelper # Built-in mixin

def test_swatch_widget # Unit testing
with_test_terminal(10, 3) do
RatatuiRuby.draw do |frame|
frame.render_widget(Swatch.new(:red), frame.area)
end
assert_cell_style 2, 1, char: "█", bg: :red
end
end

def test_input_hex # Integration testing
with_test_terminal do
inject_keys "#", "f", "f", "0", "0", "0", "0"
inject_keys :enter, :q
ColorPicker.new.run
assert_snapshots "after_hex_entry"
end
end
end

One Include, Everything Works
The module sets up a headless terminal, injects events, and asserts on rendered output. Everything
runs in-process with no external dependencies.
What's inside:

Headless
terminal No real TTY needed
Snapshots
Plain text and rich (ANSI colors)
Event injection Keys, mouse, paste, resize
Style assertions Color, bold, underline at any cell
Test doubles Mock frames and stub rects
UPDATE_SNAPSHOTS=1 Regenerate baselines in one command

Scale up
Full App Solutions
RatatuiRuby renders. For complex applications, add a framework that
manages state and composition.

Framework
Rooibos
Model-View-Update architecture. Inspired by Elm, Bubble Tea, and React +
Redux. Your UI is a
pure function of state.

Functional programming with MVU
Commands work off the main thread
Messages, not callbacks, drive updates

Explore Rooibos →

Coming Soon
Kit
Component-based architecture. Encapsulate state, input handling, and
rendering in reusable pieces.

OOP with stateful components
Separate UI state from domain logic
Built-in focus management & click handling

Watch for Kit →

Both use the same widget library, declarative styles, and rendering engine. Both
compose cleanly, test easily with snapshots and event injection, and keep state easy to reason about.
Pick the paradigm that fits your brain. You can't choose wrong.

Philosophy
Why RatatuiRuby?

Ruby deserves world-class terminal user interfaces. TUI
developers deserve a world-class language.
RatatuiRuby wraps Rust's Ratatui via native extension.
The Rust library handles rendering. Your Ruby code handles design.

Text UIs are seeing a renaissance with many new TUI libraries popping up. The Ratatui bindings have
proven to be
full featured and stable.

Mike Perham, creator of Sidekiq and Faktory

Explore the RatatuiRuby
ecosystem →

Why Rust? Why Ruby?
Rust excels at low-level rendering. Ruby excels at expressing domain logic and UI.
RatatuiRuby puts each language where it performs best.

Versus CharmRuby
CharmRuby wraps Charm's Go
libraries.
Both projects give Ruby developers TUI options.

CharmRuby
RatatuiRuby

Integration
Two runtimes, one process
Native extension in Rust

Runtime
Go + Ruby (competing)
Ruby (Rust has no runtime)

Memory
Two uncoordinated GCs
One Garbage Collector

Style
The Elm Architecture (TEA)
TEA, OOP, or Imperative

Get Started

Quickstart Beta

Examples

API Reference
Guides

Ecosystem

Rooibos Alpha

Kit Planned
Framework Planned
UI Widgets
Planned

Community

Discuss and Chat
Announcements
Development
Bug Tracker

Contribute

Contributing Guide
Code of Conduct
Project History
Pull Requests

RatatuiRuby

Source
RubyGems
Ratatui
Build Status

© 2026 Kerrick Long · Library: LGPL-3.0-or-later · Website: CC-BY-NC-ND-4.0 · Snippets: MIT-0

RatatuiRuby is a RubyGem built on Ratatui, a leading TUI library written in Rust. You get native performance with the joy of Ruby. It allows you to create rich command-line user interfaces with minimal effort.

Start small with rich moments. Add a spinner, a progress bar, or an inline menu to your CLI script. No full-screen takeover; your terminal history stays intact. Inline viewports solve the problem of disappearing UI elements on exit, preserving scrollback. They occupy a fixed number of lines, render rich UI, and leave the output in place when done.

The `Spinner` class demonstrates this with a simple, configurable spinner. It uses RatatuiRuby to draw a rotating sequence of characters in the terminal. The class utilizes the `run` method to manage the terminal, drawing a progress indicator with configurable characters and a timer. It also includes functions for ending the timer and displaying a confirming message.

The `RadioMenu` class provides a more complex example, rendering an inline menu with selectable options. It uses the `block` widget to display a title and navigation controls, enabling users to select an environment – Production, Staging, or Development. It showcases the ability to handle keyboard events, allowing the user to navigate and choose an option. The code includes mechanisms like moving the cursor up or down, confirming the selection, and even canceling the process through Ctrl+C. Key event injection is handled, allowing the system to accept a wide range of input.

Beyond these examples, RatatuiRuby provides various built-in widgets, including a chart, gauge, and list. Furthermore, it supports a full application framework with Rooibos—a Model-View-Update architecture—and later, an upcoming component library called Kit. The code also bundles a headless terminal, event injection, snapshots, and style assertions for easy testing. All of these features are designed to offer a full-featured experience for creating TUI applications.

Finally, RatatuiRuby is backed by a solid community and a robust development process, reflecting the collective efforts of its contributors and the project's overall vision: to provide Ruby developers with a powerful and flexible tool for building high-quality terminal user interfaces.