A Clojure TUI (Terminal User Interface) library inspired by Bubble Tea.
Build terminal applications using the Elm Architecture (Model-Update-View pattern) with a simple, functional API. Run them on the JVM, as a native-image binary or on babashka.
This library is vibecoded and very early. It works for the examples but please let me know if you encounter any issues. I am planning to use it for something more sophisticated. Please expect breaking changes.
Add to your deps.edn:
via github
{:deps {io.github.timokramer/charm.clj {:git/tag "v0.1.64" :git/sha "e5120a9"}}}
or via clojars maven repository
{:deps {de.timokramer/charm.clj {:mvn/version "0.1.64"}}}
(ns myapp.core
(:require [charm.core :as charm]))
(defn update-fn [state msg]
(cond
(charm/key-match? msg "q") [state charm/quit-cmd]
(charm/key-match? msg "k") [(update state :count inc) nil]
(charm/key-match? msg "j") [(update state :count dec) nil]
:else [state nil]))
(defn view [state]
(str "Count: " (:count state) "\n\n"
"j/k to change, q to quit"))
(charm/run {:init {:count 0}
:update update-fn
:view view})
(charm/run {:init initial-state-or-fn
:update (fn [state msg] [new-state cmd])
:view (fn [state] "string to render")
;; Options
:alt-screen false ; Use [alternate screen buffer](#alternate-screen-buffer)
:mouse :cell ; Mouse mode: nil, :normal, :cell, :all
:focus-reporting false ; Report focus in/out events
:fps 60}) ; Frames per second
Terminals have two screen buffers: the normal buffer (where your shell history lives) and the alternate buffer (a separate, clean screen).
When you set :alt-screen true, your program switches to the alternate buffer on startup and switches back when it exits. This is the same mechanism used by vim, less, and htop — your previous terminal content disappears while the program runs and reappears when it quits, as if nothing happened.
Use :alt-screen true for full-screen applications like file browsers, editors, or dashboards. Leave it false (the default) for inline programs like prompts or spinners that should leave their output visible in the scrollback after they finish.
Messages are maps with a :type key. Built-in message types:
;; Check message types
(charm/key-press? msg) ; Keyboard input
(charm/mouse? msg) ; Mouse event
(charm/window-size? msg) ; Terminal resized
(charm/quit? msg) ; Quit signal
;; Match specific keys
(charm/key-match? msg "q") ; Letter q
(charm/key-match? msg "ctrl+c") ; Ctrl+C
(charm/key-match? msg "enter") ; Enter key
(charm/key-match? msg :up) ; Arrow up
;; Check modifiers
(charm/ctrl? msg)
(charm/alt? msg)
(charm/shift? msg)
Commands are async functions that produce messages:
;; Quit the program
charm/quit-cmd
;; Create a custom command
(charm/cmd (fn [] (charm/key-press :custom)))
;; Run multiple commands in parallel
(charm/batch cmd1 cmd2 cmd3)
;; Run commands in sequence
(charm/sequence-cmds cmd1 cmd2 cmd3)
(require '[charm.core :as charm])
;; Create a style
(def my-style
(charm/style :fg charm/red
:bold true
:padding [1 2]))
;; Apply style to text
(charm/render my-style "Hello!")
;; Shorthand
(charm/styled "Hello!" :fg charm/green :italic true)
;; Colors
(charm/rgb 255 100 50) ; True color
(charm/hex "#ff6432") ; Hex color
(charm/ansi :red) ; ANSI 16 colors
(charm/ansi256 196) ; 256 palette
;; Borders
(charm/render (charm/style :border charm/rounded-border) "boxed")
;; Layout
(charm/join-horizontal :top block1 block2)
(charm/join-vertical :center block1 block2)
Please take a look at the examples in the docs folder and don't hesitate to contribute your examples please.
charm.clj/
├── src/charm/
│ ├── core.clj ; Public API
│ ├── program.clj ; Event loop
│ ├── terminal.clj ; JLine wrapper
│ ├── message.clj ; Message types
│ ├── ansi/
│ │ ├── parser.clj ; ANSI sequence parsing
│ │ └── width.clj ; Text width calculation
│ ├── input/
│ │ ├── keys.clj ; Key sequence mapping
│ │ ├── mouse.clj ; Mouse event parsing
│ │ └── handler.clj ; Input reading
│ ├── style/
│ │ ├── core.clj ; Style API
│ │ ├── color.clj ; Color definitions
│ │ ├── border.clj ; Border styles
│ │ └── layout.clj ; Padding, margin, alignment
│ └── render/
│ ├── core.clj ; Renderer
│ └── screen.clj ; ANSI sequences
└── test/charm/ ; Tests
clojure -M:test
MIT
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |