dab0a27e4de389830b667bc50f93c2f54a0f74e0
Includes getting started guide, hiccup views reference, full API documentation, and annotated example walkthroughs with ASCII output examples. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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.
╭──────────────────────────────────────╮
│ │
│ ╭────────────────────╮ │
│ │ Counter: 42 │ │
│ ╰────────────────────╯ │
│ │
│ j/k: change r: reset q: quit │
│ │
╰──────────────────────────────────────╯
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
Running Examples
# With Babashka (simple sync runtime)
bb counter
bb timer
bb list
bb spinner
bb views
bb http
# With Clojure (full async support)
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
Description
Languages
Clojure
100%