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-redetc. 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
leinitself 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 byciderfor inspector output and by deps.edn tooling. Configurable palette via{:color-scheme {:keyword [:bold :cyan] :string [:green]}}. Switch to plainpuget.printer/pprintwhen 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-routesSystem/outthrough 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"))