Liking cljdoc? Tell your friends :D

charm.clj

Status Clojars Project

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.

Status

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.

Features

  • Elm Architecture - Simple init/update/view pattern for predictable state management
  • UI Components - Spinner, text-input, list, paginator, timer, progress, help
  • Styling - Colors (ANSI, 256, true color), borders, padding, alignment
  • Input handling - Keyboard and mouse events with modifier support
  • Efficient rendering - Line diffing for minimal terminal updates
  • core.async - Asynchronous command execution

Documentation

Installation

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"}}}

Quick Start

(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})

API Overview

Running a Program

(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

Alternate Screen Buffer

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

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

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)

Styling

(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)

Examples

Please take a look at the examples in the docs folder and don't hesitate to contribute your examples please.

Project Structure

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

Testing

clojure -M:test

Dependencies

License

MIT

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close