Adam Jeniski 4a05130488
SCIP Index / index (push) Successful in 1m38s
Add package-map for cross-repo SCIP navigation
- Add package-map.edn mapping tui.* namespaces to io.github.ajet/clojure-tui
- Update CI workflow to pass package-map when generating SCIP index
- Enables cross-repo navigation from dependent projects in Sourcegraph

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 23:10:03 -05:00
2026-01-21 12:58:50 -05:00
2026-01-23 07:56:25 -05:00
2026-02-03 13:01:10 -05:00
2026-02-03 13:01:10 -05:00
2026-02-03 12:53:52 -05:00
2026-02-03 16:49:35 -05:00
2026-01-21 10:30:07 -05:00
2026-01-23 07:56:25 -05:00
2026-01-21 10:30:07 -05:00
2026-02-03 19:02:49 -05:00

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 Example

Features

  • Elm Architecture - Predictable state management with init, update, and view
  • Hiccup Views - Declarative UI with familiar Clojure syntax
  • Async Commands - Timers, batched operations, and custom async functions
  • 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
  • Babashka Compatible - Fast startup with Babashka or full Clojure

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.core :as tui]))

(defn update-fn [model msg]
  (if (tui/key= msg "q")
    [model tui/quit]
    [model nil]))

(defn view [model _size]
  [: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.core :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 _size]
  [: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.

Counter

bb counter

Timer

Countdown timer with pause/resume - demonstrates async commands.

Timer

bb timer

List Selection

Multi-select list with cursor navigation.

List Selection

bb list

Spinner

Animated loading spinners with multiple styles.

Spinner

bb spinner

Views

State machine pattern with multiple views and navigation.

Views

bb views

HTTP

Async HTTP requests with loading states.

HTTP

bb http

Running Examples

Run examples with Babashka (recommended for fast startup):

bb counter
bb timer

Or with full Clojure:

clojure -A:dev -M -m examples.counter

Documentation

Architecture

View (hiccup) → Render (ANSI string) → Terminal (raw mode I/O)
     ↑                                        │
     │                                        v
   Model  ←──────── Update ←─────────── Input (key parsing)

The application follows the Elm Architecture:

  1. Model - Your application state (any Clojure data structure)
  2. Update - A pure function (fn [model msg] [new-model cmd]) that handles messages
  3. View - A pure function (fn [model size] hiccup) that renders the UI, where size is {:width w :height h}

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/after ms msg) Send msg 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      # Main runtime (Elm architecture + async commands)
    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
TUI 🤝 elm architecture 🤝 hiccup
Readme 661 KiB
Languages
Clojure 100%