Created tape files and GIFs demonstrating each example: - counter, timer, list, spinner, views, http - Updated README with GIF showcase Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
5.8 KiB
Clojure TUI
A terminal user interface framework for Clojure, inspired by Bubbletea (Go). Build interactive CLI applications using the Elm Architecture with Hiccup-style views.
Features
- Elm Architecture - Predictable state management with
init,update, andview - Hiccup Views - Declarative UI with familiar Clojure syntax
- Two Runtimes - Full async (
tui.core) or simple sync (tui.simplefor Babashka) - Rich Styling - Colors (16, 256, true color), bold, italic, underline, and more
- Layout System - Rows, columns, and boxes with borders
- Input Handling - Full keyboard support including arrows, function keys, and modifiers
Quick Start
Installation
Add to your deps.edn:
{:deps {io.github.yourname/clojure-tui {:git/tag "v0.1.0" :git/sha "..."}}}
Hello World
(ns myapp.core
(:require [tui.simple :as tui]))
(defn update-fn [model msg]
(if (tui/key= msg "q")
[model tui/quit]
[model nil]))
(defn view [model]
[:col
[:text {:fg :cyan :bold true} "Hello, TUI!"]
[:text {:fg :gray} "Press q to quit"]])
(tui/run {:init {}
:update update-fn
:view view})
Output:
Hello, TUI!
Press q to quit
Counter Example
(ns myapp.counter
(:require [tui.simple :as tui]))
(defn update-fn [model msg]
(cond
(tui/key= msg "q") [model tui/quit]
(tui/key= msg "k") [(inc model) nil]
(tui/key= msg "j") [(dec model) nil]
(tui/key= msg "r") [0 nil]
:else [model nil]))
(defn view [model]
[:col
[:box {:border :rounded :padding [0 2]}
[:text {:fg :yellow :bold true} (str "Count: " model)]]
[:text {:fg :gray} "j/k: change r: reset q: quit"]])
(tui/run {:init 0
:update update-fn
:view view})
Output:
╭──────────────╮
│ Count: 0 │
╰──────────────╯
j/k: change r: reset q: quit
Examples
Counter
Simple counter demonstrating basic Elm Architecture.
bb counter
Timer
Countdown timer with pause/resume - demonstrates async commands.
bb timer
List Selection
Multi-select list with cursor navigation.
bb list
Spinner
Animated loading spinners with multiple styles.
bb spinner
Views
State machine pattern with multiple views and navigation.
bb views
HTTP
Async HTTP requests with loading states.
bb http
Running with Full Clojure
For full async support (core.async), run with Clojure:
clojure -A:dev -M -m examples.counter
Documentation
- Getting Started - Installation, first app, core concepts
- Hiccup Views - View elements, styling, and layout
- API Reference - Complete API documentation
- Examples - Annotated example applications
Architecture
View (hiccup) → Render (ANSI string) → Terminal (raw mode I/O)
↑ │
│ v
Model ←──────── Update ←─────────── Input (key parsing)
The application follows the Elm Architecture:
- Model - Your application state (any Clojure data structure)
- Update - A pure function
(fn [model msg] [new-model cmd])that handles messages - View - A pure function
(fn [model] hiccup)that renders the UI
Two Runtimes
tui.simple - Synchronous (Babashka-compatible)
Best for simple applications. No async support, but works with Babashka.
(require '[tui.simple :as tui])
(tui/run {:init model :update update-fn :view view-fn})
tui.core - Asynchronous (Full Clojure)
Full-featured runtime with async commands like timers and batched operations.
(require '[tui.core :as tui])
(defn update-fn [model msg]
(case msg
:tick [(update model :seconds inc) (tui/tick 1000)]
[model nil]))
(tui/run {:init {:seconds 0}
:update update-fn
:view view-fn
:init-cmd (tui/tick 1000)})
Hiccup View Elements
| Element | Description | Example |
|---|---|---|
:text |
Styled text | [:text {:fg :red :bold true} "Error"] |
:row |
Horizontal layout | [:row "Left" "Right"] |
:col |
Vertical layout | [:col "Line 1" "Line 2"] |
:box |
Bordered container | [:box {:border :rounded} "Content"] |
:space |
Empty space | [:space {:width 5}] |
Commands
Commands are returned from your update function to trigger side effects:
| Command | Description |
|---|---|
tui/quit |
Exit the application |
(tui/tick ms) |
Send :tick after ms milliseconds |
(tui/batch c1 c2) |
Run commands in parallel |
(tui/sequentially c1 c2) |
Run commands sequentially |
(fn [] msg) |
Custom async function returning a message |
Key Matching
(tui/key= msg "q") ;; Character
(tui/key= msg :enter) ;; Special key
(tui/key= msg :up) ;; Arrow
(tui/key= msg [:ctrl \c]) ;; Control combo
(tui/key= msg [:alt \x]) ;; Alt combo
Project Structure
src/
tui/
core.clj # Full async runtime (core.async)
simple.clj # Simple sync runtime (Babashka-compatible)
render.clj # Hiccup → ANSI
terminal.clj # Raw mode, input/output
input.clj # Key parsing
ansi.clj # ANSI codes, colors
examples/
counter.clj
timer.clj
list_selection.clj
spinner.clj
views.clj
http.clj
License
MIT





