Skip to main content
ansicode
Clojure

ANSI escape codes in Clojure — \u001b, clansi, io.aviso/pretty

Clojure inherits Java's string-literal rules — only the Unicode escape `\u####` works. `\e` is not supported, `\x1b` is not supported, `\033` is not supported. Write `"\u001b[31m"` and lowercase the hex digits after `\u` (Clojure's reader is case-sensitive about Unicode escape forms). `(println "\u001b[1;31merror:\u001b[0m permission denied")` works on every JVM-hosted Clojure ≥ 1.0 and on ClojureScript (which compiles to JS where `"\u001b"` is also valid). On macOS, Linux, BSDs, and Windows 10+ with Conhost 1709+ / Windows Terminal, terminals parse ANSI natively. For the idiomatic helper reach for **`clansi`** — the minimal classic Clojure ANSI library — `(clansi.core/style "error" :red :bold)` returns a styled string with the SGR prefix and reset baked in. For Leiningen-grade structured output and coloured stack traces reach for **`io.aviso/pretty`** — the library Leiningen itself uses for its own coloured error formatting; `io.aviso.ansi/red`, `io.aviso.ansi/bold-red`, plus `io.aviso.exception/format-exception` for pretty stack traces. For coloured pretty-printing of data structures reach for **`mvxcvi/puget`** — the canonical Clojure pretty-printer, used by `cider` for inspector output and by deps.edn tooling for human-readable EDN; `(puget.printer/cprint value)` emits ANSI-coloured indented EDN. For Windows VT enablement (pre-Conhost-1709 builds) and JVM-portable terminal capability use **`jansi`** via Java interop — `(org.fusesource.jansi.AnsiConsole/systemInstall)` enables VT on every JVM-capable platform. Capability gating: `(some? (System/console))` is the JVM-portable check (returns `nil` when stdin/stdout is redirected). Pair with `(System/getenv "NO_COLOR")` for the env-var convention. **CIDER nREPL caveat (page's primary SERP differentiator)**: editor-attached REPLs (CIDER in Emacs, Calva in VSCode, Cursive in IntelliJ) speak the nREPL bencode protocol — they consume the result `:value` field and render it themselves, **not** by piping raw stdout. ANSI escapes inside the `:value` string pass through unchanged but most editor REPLs display them as raw text. Terminal-attached REPLs (`lein repl`, `clj`, `bb`) DO parse ANSI in stdout. The fix when you actually need coloured output in an editor REPL is to print to `*err*` (which CIDER passes through verbatim so the host terminal renders the colour) or to install `cider-nrepl`'s `print-color` middleware that maps SGR codes to editor face overlays.

Recommended libraries

  • clansi

    Minimal classic Clojure ANSI library. `(clansi.core/style "error" :red :bold)` returns a styled string with the SGR prefix and reset baked in. Style keywords: `:red`/`:green`/`:blue`/`:yellow`/`:magenta`/`:cyan`/`:white`, `:bg-red` etc. for backgrounds, `:bold`/`:underline`/`:reverse`/`:blink`/`:reset`. Zero magic — composes via plain string concatenation. The standard pick when you don't want a heavier library.

  • io.aviso/pretty

    Leiningen-grade structured output — the library `lein` itself uses for its coloured error formatting. `io.aviso.ansi/red`, `io.aviso.ansi/bold-red`, `io.aviso.ansi/cyan-bg`, etc. — named SGR helpers. `(io.aviso.exception/format-exception e)` renders a coloured, source-file-aware stack trace far more readable than the default JVM trace. The de-facto canonical answer when you want exception output that doesn't look like a Java enterprise wall of text.

  • mvxcvi/puget

    Canonical Clojure pretty-printer with ANSI colour output. `(puget.printer/cprint value)` emits ANSI-coloured indented EDN — used by `cider` for inspector output and by deps.edn tooling. Configurable palette via `{:color-scheme {:keyword [:bold :cyan] :string [:green]}}`. Switch to plain `puget.printer/pprint` when stdout isn't a TTY. The right call when you need readable output of nested data structures.

  • jansi (Java interop)

    JVM-portable ANSI library — the Java side of the Clojure stack. `(org.fusesource.jansi.AnsiConsole/systemInstall)` enables Windows VT mode on pre-Conhost-1709 builds and re-routes `System/out` through an ANSI-aware wrapper. `(org.fusesource.jansi.Ansi/ansi)` provides a fluent builder API accessible via Clojure's `.` interop. Pull in when you need pre-Windows-10-1709 support or are wrapping a Java library that already uses jansi.

Idiomatic patterns

Direct println with \u001b — Java rules, ClojureScript-compatible
;; Clojure inherits Java string-literal rules — only \u####
;; Unicode escapes work. \e / \x1b / \033 are NOT supported.
;; Hex digits after \u must be lowercase: \u001b not \u001B.
;; The same syntax compiles unchanged on ClojureScript.

(ns example.ansi)

(defn -main []
  (println "\u001b[1;31merror:\u001b[0m permission denied")
  (println "\u001b[33mwarn:\u001b[0m deprecated flag")
  (println "\u001b[32mok:\u001b[0m 142 tests passed")

  ;; Truecolor — 38;2;R;G;B
  (println "\u001b[38;2;255;128;0morange truecolor\u001b[0m"))

;; Reusable helper — wraps SGR prefix + reset around text:
(defn esc [sgr text]
  (str "\u001b[" sgr "m" text "\u001b[0m"))

(println (esc "1;31" "FATAL") "server crashed")
(println (esc "32"   "ok:")    "142 tests passed")
(println (esc "38;2;255;128;0" "truecolor orange"))
clansi + io.aviso/pretty — style keywords + coloured exception trace
;; deps.edn:
;;   {:deps {clansi/clansi      {:mvn/version "1.0.0"}
;;           io.aviso/pretty    {:mvn/version "1.4.4"}}}
;;
;; project.clj:
;;   :dependencies [[clansi "1.0.0"]
;;                  [io.aviso/pretty "1.4.4"]]

(require '[clansi.core :as ansi]
         '[io.aviso.ansi :as aansi]
         '[io.aviso.exception :as aex])

;; clansi/style wraps text with named SGR + reset.
;; Keywords compose left-to-right inside one call.
(println (ansi/style "error: " :red :bold) "permission denied")
(println (ansi/style "ok: " :green)        "142 tests passed")
(println (ansi/style " CAUTION " :black :bg-yellow) " brakes wet")

;; io.aviso.ansi — named helpers (same vocabulary Leiningen
;; uses internally for its own coloured output):
(println (aansi/red "error:")        "permission denied")
(println (aansi/bold-red "FATAL")    "server crashed")
(println (aansi/bold-underlined "Section 1"))

;; Pretty-print an exception with coloured stack frames —
;; THE Clojure-idiomatic way to render exceptions to a TTY:
(try
  (/ 1 0)
  (catch ArithmeticException e
    (println (aex/format-exception e))))
mvxcvi/puget — coloured pretty-print of nested data structures
;; deps.edn: {:deps {mvxcvi/puget {:mvn/version "1.3.4"}}}

(require '[puget.printer :as puget])

(def data
  {:users [{:name "alice" :email "[email protected]" :age 30}
           {:name "bob"   :email "[email protected]" :age 25}]
   :counts {:active 42 :inactive 7}
   :timestamp #inst "2026-05-19T10:00:00Z"})

;; cprint = "coloured print" — ANSI-coloured indented EDN
;; (default palette matches CIDER's inspector colours):
(puget/cprint data)

;; cprint-str returns the coloured string instead of printing:
(let [out (puget/cprint-str data)]
  (spit "snapshot.txt" out))

;; Switch to plain pprint when stdout isn't a TTY:
(if (some? (System/console))
  (puget/cprint data)
  (puget/pprint data))

;; Custom palette — override individual SGR codes per type:
(puget/cprint data
              {:print-color true
               :color-scheme {:keyword [:bold :cyan]
                              :string  [:green]
                              :number  [:yellow]
                              :nil     [:bold :black]}})
Capability gate + CIDER nREPL vs lein repl ANSI passthrough caveat
;; Capability gating in Clojure:
;;   (some? (System/console))      — JVM-portable, returns nil
;;                                    when stdin/stdout redirected
;;   (System/getenv "NO_COLOR")    — universal env-var convention
;;   (System/getenv "FORCE_COLOR") — universal opt-in
;;
;; CIDER nREPL CAVEAT (the #1 "my colours don't show up in CIDER"
;; friction point):
;;
;; Terminal-attached REPLs (lein repl, clj, bb) speak raw stdout,
;; so ANSI passes through and the host terminal parses it.
;;
;; Editor-attached REPLs (CIDER in Emacs, Calva in VSCode,
;; Cursive in IntelliJ) speak the nREPL bencode protocol — they
;; consume the result :value field and render it themselves.
;; ANSI bytes inside :value pass through unchanged but most
;; editor REPLs display them as raw text.
;;
;; Fix when you actually need coloured output in an editor REPL:
;;   1) Print to *err* — CIDER passes stderr through verbatim,
;;      so the host terminal (not the editor buffer) renders it.
;;   2) Install cider-nrepl's print-color middleware, which
;;      maps SGR codes to editor face overlays.

(defn ansi-capable? []
  (cond
    (System/getenv "NO_COLOR")    false
    (System/getenv "FORCE_COLOR") true
    :else (some? (System/console))))

(defn styled [text sgr]
  (if (ansi-capable?)
    (str "\u001b[" sgr "m" text "\u001b[0m")
    text))

(println (styled "OK"   "32"))
(println (styled "FAIL" "1;31"))

;; Force-render to the host terminal even when CIDER is the
;; attached REPL — *err* is passed through verbatim:
(binding [*out* *err*]
  (println (styled "DEBUG" "36") "checkpoint hit"))

Related sequences

Other languages