๐Ÿ’Ž Opal

Next-Generation Terminal User Interface (TUI) & CLI DSL Framework for Crystal

CI Docs Crystal License: MIT

Pure Crystal. Zero external C library dependencies (no ncurses). Native Windows, Linux, and macOS support.


๐ŸŒŸ What is Opal?

Opal is an all-in-one terminal framework for Crystal designed to build world-class command-line interfaces, micro-interactive prompts, and full-screen terminal applications.

It merges the best paradigms from modern terminal engineering into a cohesive, idiomatic Crystal DSL:


๐Ÿ“ฆ Installation

Add Opal to your project's shard.yml:

dependencies:
  opal:
    github: sol-vin/opal
    version: ~> 0.1.0

Then install dependencies:

shards install

Require Opal in your Crystal code:

require "opal"

๐Ÿ“‘ Table of Contents


๐Ÿš€ Quick Start

1. Build an Interactive Setup Wizard with Opal.form

require "opal"

result = Opal.form("Project Setup") do |f|
  f.text "name", "Project Name:", default: "my-app", required: true
  f.password "token", "API Token:", min_length: 8
  f.select "db", "Database:", ["PostgreSQL", "SQLite", "MySQL"]
  f.multi_select "addons", "Addons:", ["Redis", "Elasticsearch", "GraphQL"]
  f.confirm "deploy", "Auto-deploy to staging?", default: true
end

if config = result
  puts "Created project #{config["name"]} using #{config["db"]}!"
end

2. Live Fuzzy Filter with Split Preview

require "opal"

branches = ["main", "staging", "feat/auth", "feat/fuzzy-finder", "fix/timeout"]

selected = Opal.filter(
  items: branches,
  title: "Git Switcher",
  preview: ->(b : String) { "Branch #{b}\nStatus: Clean\nUpdated: 5m ago" }
)

puts "Switched to branch: #{selected}" if selected

๐Ÿ“ Multi-Field Form & Wizard DSL

Traditional CLI prompts ask one question at a time and prevent reviewing earlier inputs. Opal.form presents an interactive card where all fields are visible simultaneously, users navigate using Tab / Shift+Tab, and live validation catches mistakes instantly.

result = Opal.form("New Microservice") do |f|
  f.text "service", "Service Name:", required: true
  f.text "port", "HTTP Port:", default: "8080"
  f.password "secret", "Secret Key:", min_length: 6
  f.select "tier", "Hosting Tier:", ["Small", "Medium", "Large"]
  f.multi_select "plugins", "Plugins:", ["Metrics", "Tracing", "Auth"]
  f.confirm "enabled", "Enable service immediately?", default: true

  # Custom real-time validation
  f.validate "port" do |val|
    (val.to_i? && (1024..65535).includes?(val.to_i)) ? nil : "Port must be 1024-65535"
  end
end

๐Ÿ” Live Fuzzy Search & Filter

Fast, keystroke-responsive fuzzy filtering inspired by fzf:

choice = Opal.filter(
  items: Dir["src/**/*.cr"],
  title: "Fuzzy File Finder",
  preview: ->(path : String) { File.read(path).lines.first(15).join("\n") }
)

๐Ÿ“Š Data Visualizations

Render rich dashboards and metrics without graphics libraries:

Sparklines

# Output:  โ–‚โ–„โ–‡โ–‡โ–ˆโ–†โ–„โ–ƒโ–‚โ–‚
puts Opal::UI::Sparkline.render_to_string([10.0, 15.0, 25.0, 80.0, 95.0, 60.0])

BarCharts, Gauges & Trees in UI Trees

Opal.render_ui(width: 70, height: 20) do |ui|
  ui.vstack(spacing: 1) do |v|
    # Percentage Gauge
    v.gauge 0.76, label: "Disk Usage", color: :yellow

    # Horizontal Bar Chart
    v.barchart(title: "Memory Allocation") do |bc|
      bc.bar "Web", 420, color: :green
      bc.bar "Worker", 850, color: :cyan
      bc.bar "DB", 1200, color: :red
    end

    # Hierarchical Tree
    v.tree(title: "Service Graph") do |t|
      t.node("API Gateway", icon: "๐ŸŒ") do |gateway|
        gateway.add("Auth Service", icon: "๐Ÿ”’")
        gateway.add("Search Node", icon: "๐Ÿ”")
      end
    end
  end
end

๐ŸชŸ Buffer Blitting, Modals & Toasts

Floating Modal Dialog

Center a dialog box over any screen buffer with automatic background dimming:

modal = Opal::UI::Modal.new(
  title: "Confirm Deletion",
  message: "Are you sure you want to drop database 'prod'?",
  buttons: ["Cancel", "Confirm Drop"],
  selected_button: 1
)
modal.render(buffer, 0, 0, 80, 24)

Toast Notifications

Stack floating alerts in the top-right corner with auto-dismiss timers:

toasts = Opal::UI::ToastManager.new
toasts.add("Build Succeeded", "All 124 tests passed", level: :success, duration_ms: 3000)
toasts.add("Disk Warning", "Free space below 10%", level: :warning)

toasts.render_overlay(buffer, position: :top_right)

๐Ÿ“– Terminal Markdown Viewer

Convert Markdown documents into styled ANSI terminal text:

doc = <<-MD
# Opal Framework v1.0
Welcome to **Opal**! Build *beautiful* CLIs in Crystal.

### Features
- Zero external C dependencies
- 60fps delta rendering

```crystal
require "opal"
puts "Hello world!"

"Simplicity is prerequisite for reliability." MD

puts Opal.render_markdown(doc, width: 80)


---

## ๐ŸŽจ Theme Engine & Semantic Colors

Opal includes pre-registered designer palettes and semantic color tokens:

```crystal
# Switch theme dynamically
Opal.theme = :catppuccin_mocha # :dracula, :nord, :tokyo_night, :gruvbox, etc.

# Semantic color tokens
theme = Opal.theme
style = Opal.style
  .foreground(theme.primary)
  .background(theme.surface)
  .border_foreground(theme.border)

๐Ÿ” Command Palette Overlay

Press Ctrl+P or Ctrl+K to summon an instant Spotlight action launcher:

palette = Opal::UI::CommandPalette.new
palette.add("git:commit", "Commit changes", category: "Git", shortcut: "ctrl+c") { commit_flow }
palette.add("file:open", "Open file picker", category: "File", shortcut: "ctrl+o") { open_picker }

๐Ÿ”— OSC 8 Hyperlinks & OSC 52 Clipboard

# Clickable hyperlink in modern terminals
puts Opal.hyperlink("View Source on GitHub", "https://github.com/sol-vin/opal")

# Copy directly to OS desktop clipboard over SSH and local sessions
Opal.copy_to_clipboard("API_KEY_SECRET_12345")

โฑ๏ธ Animation & Easing Engine

Smooth transitions, progress bars, and color interpolation:

# Easing functions
t = Opal::Animation.ease(:ease_in_out_cubic, 0.5)

# Color interpolation (e.g. green to red as CPU load increases)
normal_color = Opal::Color.green
alert_color  = Opal::Color.red
current_c    = Opal::Color.lerp(normal_color, alert_color, 0.75)

๐Ÿ› ๏ธ CLI Application DSL

app = Opal.cli("deployer", "Cloud deployment manager", "0.4.0") do
  option "-v", "--verbose", "Enable debug logging", type: :bool

  command "deploy", "Deploy application containers" do
    argument "service", "Service name to deploy"
    option "-c", "--concurrency=NUM", "Max concurrency", type: :int, default: 3

    run do |ctx|
      svc = ctx.argument("service")
      puts "Deploying #{svc} (concurrency: #{ctx.int("concurrency")})..."
    end
  end
end

app.run(ARGV)

๐Ÿต The Elm Architecture (TEA)

Build reactive terminal applications with pure state transitions:

record CounterModel, count : Int32 = 0 do
  include Opal::Tea::Model

  def init : Opal::Tea::Cmd
    Opal::Tea::Cmd.none
  end

  def update(msg : Opal::Tea::Msg) : {Opal::Tea::Model, Opal::Tea::Cmd}
    case msg
    when Opal::Tea::KeyMsg
      case msg.key
      when "up", "k"   then {CounterModel.new(count + 1), Opal::Tea::Cmd.none}
      when "down", "j" then {CounterModel.new(count - 1), Opal::Tea::Cmd.none}
      when "q"         then {self, Opal::Tea::Cmd.quit}
      else {self, Opal::Tea::Cmd.none}
      end
    else
      {self, Opal::Tea::Cmd.none}
    end
  end

  def view : String
    "Counter: #{count} (Press โ†‘/k, โ†“/j, q to quit)"
  end
end

Opal::Tea::Program.new(CounterModel.new).run

๐Ÿงช Testing with MockDriver

Test full TUI interactions headlessly without opening an actual terminal:

require "spec"
require "opal"

describe "Counter" do
  it "increments on keypress" do
    model = CounterModel.new
    new_model, cmd = model.update(Opal::Tea::KeyMsg.new("k"))
    new_model.as(CounterModel).count.should eq(1)
  end
end

๐Ÿ“‚ Examples

Explore all runnable examples in the examples/ directory:

Run any example:

crystal run examples/06_rich_form_wizard.cr
crystal run examples/08_dataviz_dashboard.cr
crystal run examples/09_markdown_and_overlays.cr

๐ŸŒ Cross-Platform Support

| Platform | Terminal Backend | Colors | Mouse Support | | :--- | :--- | :--- | :--- | | Linux | POSIX termios, VT100, SGR | TrueColor, 256, ANSI 16 | Yes (SGR 1006) | | macOS | POSIX termios, VT100, SGR | TrueColor, 256, ANSI 16 | Yes (SGR 1006) | | Windows | Win32 Console API (ENABLE_VIRTUAL_TERMINAL_PROCESSING) + ANSI VT100 | TrueColor, 256, ANSI 16 | Yes (SGR 1006) |


๐Ÿ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.