ori
ori
A component-based TUI framework for Crystal. Immediate-mode architecture — state lives in your App, render builds the tree each frame, callbacks sync back.
Install
Add to shard.yml:
dependencies:
ori:
github: baltavay/ori
Quick Start
require "ori"
class MyApp < Ori::App
def render : Ori::Box
box(fg: "#cdd6f4", bg: "#1e1e2e") do
text "Hello, world!", bold: true
end
end
end
MyApp.run
Widgets
Text
text "Hello", fg: "#cdd6f4", bold: true, dim: false, align: :left
Input
input(value: @name, placeholder: "Name...", border: true, on_change: ->(v : String) { @name = v; nil })
Text Area
Multi-line text editor. on_change receives (value, cursor_row, cursor_col).
area(value: @bio, on_change: ->(v : String, r : Int32, c : Int32) { @bio = v; @row = r; @col = c; nil })
Dropdown / Select
dropdown(options: ["Red", "Green", "Blue"], selected: @color_idx,
on_change: ->(i : Int32) { @color_idx = i; nil })
List
Full-featured list with cursor, scroll, checkable items.
list(items: [Ori::List::Item.new("Item 1"), Ori::List::Item.new("Item 2")],
on_select: ->(i : Int32) { puts i; nil })
Table
table(headers: ["Name", "Age"], rows: [["Alice", "30"], ["Bob", "25"]],
selected_row: @row, on_change: ->(i : Int32) { @row = i; nil })
Date Picker
date_picker(year: 2025, month: 6, day: 15, border: true,
on_change: ->(t : Time) { @date = t; nil })
DateTime Picker
datetime_picker(year: 2025, hour: 14, minute: 30, border: true,
on_change: ->(t : Time) { @dt = t; nil })
File Picker
Interactive directory browser. Navigates with j/k, enter to select, backspace/h to go up.
file_picker(flex: 1, on_select: ->(path : String) { @selected = path; nil })
Progress Bar
progress_bar(value: 50.0, max: 100.0, gradient_to: "#89b4fa")
Spinner
spinner(type: :dots)
Sparkline
sparkline(data: [1.0, 3.0, 2.0, 5.0], max: 10.0)
Viewport
Scrollable text viewer with soft-wrap and mouse wheel support.
viewport(content: long_text, on_scroll: ->(y : Int32) { @scroll_y = y; nil })
Markdown
Renders markdown content with syntax highlighting.
markdown(content: "# Hello\nWorld", on_scroll: ->(y : Int32) { nil })
Help
Keybinding reference display.
help(bindings: [Ori::HelpBinding.new("j/k", "navigate"), Ori::HelpBinding.new("q", "quit")])
Layout
Box
Container for children. Supports vertical (default) and horizontal direction.
box(border: true, padding: 1, direction: :vertical, fg: "#cdd6f4", bg: "#1e1e2e") do
text "Row 1"
text "Row 2"
end
Flex
Distribute remaining space with flex: N.
box(direction: :horizontal) do
text "Sidebar", width: 20
box(flex: 1) { text "Main content" }
end
Scrollable
box(scrollable: true, height: 10) do
50.times { |i| text "Line #{i + 1}" }
end
Keys: j/k scroll, f/b page down/up, g/G top/bottom.
Attach
Reuse a node across rebuilds (preserves internal state like cursor position).
class MyApp < Ori::App
@picker : Ori::FilePicker
def initialize
@picker = Ori::FilePicker.new(on_select: ->(path : String) { nil })
end
def render : Ori::Box
box do
attach @picker
end
end
end
Style Options
All widgets accept these style kwargs:
| Option | Type | Description |
|---|---|---|
fg |
String? |
Foreground color (#rrggbb) |
bg |
String? |
Background color |
bold |
Bool |
Bold text |
dim |
Bool |
Dim text |
border |
Bool/Symbol |
:single, :double, :rounded, :thick |
border_fg |
String? |
Border color |
padding |
Int/Tuple |
Padding (1, {2,3}, or {1,2,3,4}) |
width |
Int/String |
Fixed width or "50%" |
height |
Int/String |
Fixed height or "30%" |
flex |
Int |
Flex grow factor |
direction |
Symbol |
:vertical (default) or :horizontal |
align |
Symbol |
:left, :center, :right |
focusable |
Bool |
Receive keyboard focus |
scrollable |
Bool |
Enable scroll on box |
gradient_to |
String? |
Gradient end color |
Input Handling
Override handle_key in your app for custom key bindings:
def handle_key(key : Ori::Key) : Bool
if key.text == "q"
@running = false
return false
end
super
end
Ori::Key has text : String?, code : Ori::Key::Code (with predicates like up?, down?, enter?, tab?, etc.), and ctrl/shift/alt modifiers.
Mouse Support
Mouse events are enabled by default. Override on_mouse for custom handling:
def on_mouse(mouse : Ori::Mouse) : Bool
if mouse.action.press? && mouse.button.left?
# handle click at mouse.x, mouse.y
end
false
end
Ori::Mouse::Action: Press, Release, Wheel. Ori::Mouse::Button: Left, Middle, Right, WheelUp, WheelDown.
Navigation
- Tab / Shift+Tab — cycle focus
- Enter — activate/confirm
- Esc — cancel/close overlay
- Ctrl+C — quit
Examples
crystal run examples/hello.cr
crystal run examples/counter.cr
crystal run examples/form.cr
crystal run examples/table.cr
crystal run examples/select.cr
crystal run examples/date_picker.cr
crystal run examples/datetime_picker.cr
crystal run examples/progress_bar.cr
crystal run examples/spinner.cr
crystal run examples/sparkline.cr
crystal run examples/file_picker.cr
crystal run examples/markdown.cr
crystal run examples/viewport.cr
crystal run examples/editor.cr
crystal run examples/vim.cr
crystal run examples/todo/main.cr
crystal run examples/stopwatch.cr
crystal run examples/timer.cr
crystal run examples/splash.cr
crystal run examples/bounce.cr
Architecture
Ori uses an immediate-mode pattern:
- Your
Appholds all state renderis called each frame, building a fresh tree ofBox/Nodeobjects- Callbacks (
on_change,on_select, etc.) sync widget state back to your app - Use
attachto reuse stateful nodes (likeFilePicker,Viewport) across rebuilds
The renderer runs at 60 FPS by default (configurable via @fps). The screen does a full redraw each frame — no diffing, no artifacts.
Requirements
- Crystal >= 1.19.1
- Unix-like OS (uses termios for raw terminal mode)
Development
# Run specs
crystal spec
# Build an example
crystal build examples/hello.cr -o /tmp/hello && /tmp/hello
# Build all examples
for f in examples/*.cr examples/*/main.cr; do crystal build "$f" -o /tmp/ori_ex; done
Contributing
- Fork it (https://github.com/baltavay/ori/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
Contributors
- baltavay — creator and maintainer
License
MIT — see LICENSE
ori
- 0
- 0
- 0
- 2
- 0
- about 2 hours ago
- May 14, 2026
MIT License
Sat, 16 May 2026 04:03:59 GMT