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
;; 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"));; 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))));; 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 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"))