ratatui-ruby
This skill should be used when the user asks to "create a TUI", "terminal interface", "terminal UI", "ratatui", "ratatui-ruby", "inline viewport", "full-screen terminal app", "terminal widgets", "tui.draw", "tui.poll_event", or mentions RatatuiRuby.run, managed loop, terminal rendering, Tea MVU, or building CLI applications with rich UI elements. Should also be used when editing RatatuiRuby application files, working with terminal widgets, or discussing TUI architecture patterns.
安装与使用
请根据来源仓库和你的 AI 工具说明安装此技能。先阅读技能文件和依赖要求,再添加到工作流。
查看来源 ↗使用指南技能说明
RatatuiRuby
This skill provides guidance for building terminal user interfaces with RatatuiRuby, a Ruby gem wrapping Rust's Ratatui library. Use for TUI development, terminal widgets, layout systems, event handling, and testing terminal applications.
Quick Reference
Minimal Application
require "ratatui_ruby"
RatatuiRuby.run do |tui|
loop do
tui.draw do |frame|
widget = tui.paragraph(text: "Hello, TUI!", block: tui.block(title: "App"))
frame.render_widget(widget, frame.area)
end
case tui.poll_event
in {type: :key, code: "q"}
break
in {type: :key, code: "c", modifiers: ["ctrl"]}
break
else
# Continue
end
end
end
Key Concepts
| Concept | Purpose |
|---|---|
RatatuiRuby.run |
Managed loop handling terminal setup/teardown |
tui.draw |
Render widgets each frame |
tui.poll_event |
Capture keyboard/mouse input |
frame.area |
Available rendering area (Rect) |
frame.render_widget |
Render stateless widgets |
frame.render_stateful_widget |
Render widgets with state (List, Table) |
Two Operating Modes
| Mode | Use Case | Setup |
|---|---|---|
| Full-Screen | Complete TUI applications | RatatuiRuby.run { } (default) |
| Inline Viewport | Rich CLI moments (spinners, progress) | RatatuiRuby.run(viewport: :inline, height: 5) { } |
Full-Screen: Takes over terminal, alternate screen, restored on exit.
Inline Viewport: Preserves scrollback, fixed-height widget area, output remains visible after exit.
Core Pattern
The managed loop pattern handles terminal lifecycle:
RatatuiRuby.run do |tui|
loop do
# 1. Draw UI
tui.draw do |frame|
# Render widgets
end
# 2. Handle events
case tui.poll_event
in {type: :key, code: "q"}
break
end
end
end
Common Widgets
| Widget | Factory | Purpose |
|---|---|---|
| Paragraph | tui.paragraph(text:) |
Text display |
| Block | tui.block(title:, borders:) |
Borders, titles, padding |
| List | tui.list(items:) |
Selectable item list |
| Table | tui.table(rows:, widths:) |
Tabular data |
| Gauge | tui.gauge(ratio:) |
Progress indication |
| Tabs | tui.tabs(titles:) |
Tab navigation |
| Chart | tui.chart(datasets:) |
Data visualization |
| Canvas | tui.canvas { } |
Custom drawing |
| Scrollbar | tui.scrollbar |
Scroll indication |
Stateless vs Stateful Widgets
Stateless (Paragraph, Block, Gauge):
widget = tui.paragraph(text: "Hello")
frame.render_widget(widget, frame.area)
Stateful (List, Table):
# Create state once (outside draw loop)
list_state = tui.list_state(0)
# In draw block
list = tui.list(items: ["Item A", "Item B", "Item C"])
frame.render_stateful_widget(list, frame.area, list_state)
# Update state on input
list_state.select_next if event_down?
Block Composition
Wrap widgets with Block for borders and titles:
block = tui.block(
title: "Main",
titles: [
{content: "Help: q", position: :bottom, alignment: :right}
],
borders: [:all],
border_style: {fg: "cyan"}
)
paragraph = tui.paragraph(text: "Content", block:)
Layout
Split areas using constraints:
layout = tui.layout(
direction: :vertical,
constraints: [
tui.constraint(:percentage, 20), # Header: 20%
tui.constraint(:min, 0), # Body: remaining
tui.constraint(:length, 3) # Footer: 3 rows
]
)
chunks = layout.split(frame.area)
# chunks[0] -> header area
# chunks[1] -> body area
# chunks[2] -> footer area
Constraint Types
| Type | Syntax | Behavior |
|---|---|---|
| Length | tui.constraint(:length, 5) |
Fixed 5 rows/cols |
| Percentage | tui.constraint(:percentage, 50) |
50% of parent |
| Min | tui.constraint(:min, 10) |
At least 10 |
| Max | tui.constraint(:max, 20) |
At most 20 |
| Ratio | tui.constraint(:ratio, 1, 3) |
1/3 of space |
| Fill | tui.constraint(:fill) |
Expand into excess |
Event Handling
Pattern Matching
case tui.poll_event
in {type: :key, code: "q"}
break
in {type: :key, code: "j"} | {type: :key, code: "down"}
list_state.select_next
in {type: :key, code: "k"} | {type: :key, code: "up"}
list_state.select_previous
in {type: :key, code: "c", modifiers: ["ctrl"]}
break
in {type: :mouse, kind: "down", button: "left", x:, y:}
handle_click(x, y)
in {type: :resize}
# Terminal resized, next draw adapts
end
Event Helper Methods
event = tui.poll_event
break if event.ctrl_c?
list_state.select_next if event.down? || event.j?
Styling
Hash-based syntax for colors and modifiers:
tui.paragraph(
text: "Styled text",
style: {fg: "green", bold: true},
block: tui.block(border_style: {fg: "cyan"})
)
Text Composition
line = tui.line([
tui.span("Normal "),
tui.span("Bold", style: {bold: true}),
tui.span(" Red", style: {fg: "red"})
])
tui.paragraph(text: line)
Color Options
- Named:
"red","green","cyan","white" - Hex:
"#FF5733" - Indexed: 0-255 palette
Testing
require "ratatui_ruby/test_helper"
class MyAppTest < Minitest::Test
include RatatuiRuby::TestHelper
def test_renders_greeting
with_test_terminal(80, 24) do
RatatuiRuby.draw do |frame|
widget = RatatuiRuby::Widgets::Paragraph.new(text: "Hello")
frame.render_widget(widget, frame.area)
end
assert_snapshots("greeting") # Creates/compares snapshots/greeting.txt
end
end
def test_keyboard_navigation
with_test_terminal do
inject_keys("j", "j", "k") # Down, down, up
# Assert state changes
end
end
end
Frameworks
Tea (MVU Architecture)
Functional, Elm-style architecture for predictable state:
require "ratatui_ruby/tea"
class Counter
include RatatuiRuby::Tea::App
def init
[Model.new(count: 0), nil]
end
def view(model, tui)
tui.paragraph(text: "Count: #{model.count}")
end
def update(message, model)
case message
in {type: :key, code: "q"}
[model, RatatuiRuby::Tea::Command.exit]
in {type: :key, code: "j"}
[model.with(count: model.count + 1), nil]
else
[model, nil]
end
end
end
Counter.new.run
Best Practices
Do
- Use
RatatuiRuby.runfor managed terminal lifecycle - Create state objects outside the draw loop
- Use pattern matching for event handling
- Use inline viewports for CLI "rich moments"
- Test with
RatatuiRuby::TestHelper
Don't
- Handle Ctrl+C manually (use
event.ctrl_c?helper) - Forget to break the loop (leads to CPU spin)
- Render widgets before poll_event (blocks input)
- Use full-screen for simple progress indicators
Additional Resources
Reference Files
For detailed API documentation and patterns:
-
references/core-concepts.md- Managed loop, terminal lifecycle, inline vs full-screen -
references/widgets.md- Complete widget catalog, composition patterns -
references/layout.md- Constraints, directions, nested layouts -
references/events.md- Keyboard, mouse, event handling patterns -
references/styling.md- Colors, modifiers, text composition -
references/testing.md- TestHelper, snapshots, event injection -
references/frameworks.md- Tea MVU, Kit components
使用示例
查看来源仓库中的使用示例与配置说明。
查看来源 ↗来源历史
- 收录时间
- 2026-03-11
- 索引更新时间
- 2026-03-11
- Branch
- main
收录与同步日期属于本站记录。源码修改历史请查看来源仓库。
GitHub ↗chat Comments (0)
Sign in to join the discussion and leave a comment.
No comments yet. Be the first to share your thoughts!