crubbletea
crubbletea
A port of Charm's Bubble Tea (Go) to Crystal — a framework for building terminal UIs using the Elm Architecture (init/update/view).
This includes ports of the Bubble Tea companion libraries as well:
- Bubbles →
Crubbletea::Bubbles— UI components (text input, spinner, progress, list, table, etc.) - Lipgloss →
Crubbletea::Lipgloss— terminal styling and layout - ntcharts →
Crubbletea::Ntcharts— data visualization (line charts, bar charts, sparklines, heatmaps)
Installation
-
Add the dependency to your
shard.yml:dependencies: crubbletea: github: mansurovmaksatbek/crubbletea -
Run
shards install
Quick Start
require "crubbletea"
class Counter
include Crubbletea::Model
@count : Int32
def initialize(@count = 0)
end
def init : Crubbletea::Cmd?
nil
end
def update(msg : Crubbletea::Msg) : {Counter, Crubbletea::Cmd?}
case msg
when Crubbletea::KeyPressMsg
case msg.key.code
when .up?, .k? then {@count += 1, nil}
when .down?, .j? then {@count -= 1, nil}
when .q?, .ctrl_c? then {self, Crubbletea.quit}
else {self, nil}
end
else
{self, nil}
end
end
def view : Crubbletea::View
Crubbletea.new_view(" Count: #{@count}\n\n ↑/k increment ↓/j decrement q quit")
end
end
Crubbletea::Program(Counter).new(Counter.new).run
Architecture
Include Crubbletea::Model in your class and implement three methods:
| Method | Signature | Description |
|---|---|---|
init |
-> Cmd? |
Return initial commands (or nil) |
update |
Msg -> {Model, Cmd?} |
Handle a message, return updated model + command |
view |
-> View |
Render the current state as a string view |
Messages
Messages are the core event type. Built-in messages include:
KeyPressMsg— keyboard input withKeystruct (code, modifiers)MouseClickMsg,MouseReleaseMsg,MouseWheelMsg,MouseMotionMsg— mouse eventsWindowSizeMsg— terminal resize (width, height)QuitMsg,InterruptMsg— program exit signalsFocusMsg,BlurMsg,PasteMsg,ClearScreenMsg— other terminal events
Create custom messages by including Crubbletea::Msg:
struct MyCustomMsg
include Crubbletea::Msg
getter data : String
def initialize(@data : String)
end
end
Commands
Commands are Proc(Msg)? — async operations that produce messages:
Crubbletea.tick(1.second) { |t| TickMsg.new }
Crubbletea.every(100.milliseconds) { |t| TickMsg.new }
Crubbletea.batch([cmd1, cmd2, cmd3])
Crubbletea.sequence([cmd1, cmd2])
Crubbletea.quit
Crubbletea.clear_screen
Views
Crubbletea::View wraps a string with optional metadata:
view = Crubbletea::View.new(
content: "Hello",
alt_screen: false,
cursor: Crubbletea::Cursor.new(x: 5, y: 0),
window_title: "My App",
mouse_mode: Crubbletea::MouseMode::CellMotion,
report_focus: false
)
Lipgloss — Styling & Layout (port of Lipgloss)
style = Crubbletea::Lipgloss::Style.new
.bold(true)
.foreground(Crubbletea::Lipgloss::Color.new(0, 255, 0))
.background(Crubbletea::Lipgloss::Color.new_hex("#1a1a2e"))
.padding(1, 2)
.border(Crubbletea::Lipgloss::Border.new(Crubbletea::Lipgloss::BorderType::Rounded))
.width(40)
.align(Crubbletea::Lipgloss::Style::Pos::Center)
styled = style.render("Hello, World!")
# Layout
joined = Crubbletea::Lipgloss.join_horizontal(Crubbletea::Lipgloss::Style::Pos::Center, [left, right])
stacked = Crubbletea::Lipgloss.join_vertical(Crubbletea::Lipgloss::Style::Pos::Center, [top, bottom])
Lipgloss Layout Components
Lipgloss::Table— styled tablesLipgloss::Tree— tree viewsLipgloss::List— ordered/unordered listsLipgloss::Whitespace— spacer utility
Bubbles — UI Components (port of Bubbles)
| Component | Module | Description |
|---|---|---|
| Text Input | Bubbles::TextInput::Model |
Single-line text input with placeholder, echo modes, cursor |
| Text Area | Bubbles::TextArea::Model |
Multi-line text editor with line numbers |
| Spinner | Bubbles::Spinner::Model |
Loading spinner (LINE, DOT, MINI_DOT, PULSE, GLOBE, etc.) |
| Progress | Bubbles::Progress::Model |
Animated progress bar with spring physics |
| Table | Bubbles::TableModel |
Selectable data table |
| List | Bubbles::List::Model |
Filterable, paginated item list |
| Viewport | Bubbles::Viewport::Model |
Scrollable content area with soft wrap |
| Paginator | Bubbles::Paginator::Model |
Pagination control |
| Timer | Bubbles::Timer::Model |
Countdown timer |
| Stopwatch | Bubbles::Stopwatch::Model |
Stopwatch |
| Help | Bubbles::Help::Model |
Keybinding help display |
| File Picker | Bubbles::FilePicker::Model |
File/directory browser |
| Cursor | Bubbles::Cursor::Model |
Cursor style management |
| Key | Bubbles::Key::Model |
Key binding helper |
Ntcharts — Data Visualization (port of ntcharts)
Ntcharts::Sparkline— inline sparkline chartsNtcharts::LineChart— line charts on a canvasNtcharts::BarChart— bar chartsNtcharts::HeatMap— heat map visualizationNtcharts::Canvas— braille-based drawing canvas
Other Utilities
Harmonica— spring physics for smooth animations (Spring, Projectile)Zone— screen region tracking for click/mouse handling
Program Options
# With message filter
program = Crubbletea::Program(MyModel).new(model) do |msg|
# transform or filter messages before update
msg
end
# Send messages from outside
program.send(CustomMsg.new)
# Graceful shutdown
program.kill
Requirements
- Crystal >= 1.19.1
- Unix-like OS (uses termios for raw terminal mode)
Development
git clone https://github.com/mansurovmaksatbek/crubbletea
cd crubbletea
crystal spec
Contributing
- Fork it (https://github.com/mansurovmaksatbek/crubbletea/fork)
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create a new Pull Request
License
MIT — see LICENSE
Contributors
- Mansurov Maksatbek — creator and maintainer
Repository
crubbletea
Owner
Statistic
- 0
- 0
- 0
- 0
- 0
- about 5 hours ago
- April 27, 2026
License
MIT License
Links
Synced at
Mon, 27 Apr 2026 04:49:16 GMT
Languages