ANSI escape codes in Julia — \e, printstyled, Crayons.jl
Julia's string literals support `\e` directly — one of the few languages besides bash, zsh, perl, and php where the backslash-e escape resolves to byte 27. `"\e[31m"` is identical to `"\x1b[31m"` (hex), `"\033[31m"` (octal), and `"\u1b[31m"` (Unicode) — pick whichever reads cleanest in your code. `println("\e[1;31merror:\e[0m permission denied")` works on every Julia ≥ 0.6, on macOS, Linux, BSDs, and on Windows 10+ with Conhost 1709+ / Windows Terminal (both parse ANSI natively). For the idiomatic helper **reach into stdlib first** — `printstyled(io, xs...; color=:red, bold=true, italic=true, underline=true, blink=true, reverse=true, hidden=true)` is the Julia-canonical styled-print function. `color` accepts a named `Symbol` (`:red` / `:light_blue` / `:cyan`), an integer 0–255 for the 256-palette, or a 3-tuple `(r, g, b)` for truecolor on capable terminals. The `Base.text_colors` and `Base.disable_text_style` dicts expose the underlying SGR codes for hand-rolled output. For composable string-level styling reach for **`Crayons.jl`** — the de-facto third-party Julia ANSI library — `Crayon(foreground = :red, bold = true)` constructs a reusable style that combines via `*` (`bold_red = Crayon(foreground=:red, bold=true) * Crayon(underline=true)`), and `Box(content; foreground = :green)` wraps a value with start + reset bytes around it. For full TUIs link **`Term.jl`** (FedeClaudi) — panels, syntax-highlighted printing, progress bars, tree rendering, `tprint("[red bold]error[/red bold] denied")` markup parser. For arrow-key interactive menus the stdlib **`REPL.TerminalMenus`** module ships `RadioMenu` / `MultiSelectMenu` — used by Pkg.jl itself for interactive package selection. Capability gating: `Base.have_color` is the canonical flag — Julia sets it at startup from `--color=auto|yes|no` (`auto` is `true` when stdout is a TTY and `$NO_COLOR` is unset). Piping Julia output to `less -R` works as expected. **Crucial caveat**: under Jupyter / IJulia / Pluto notebook front-ends `Base.have_color` is `false` and ANSI escapes pass through as raw text — switch to an HTML or MIME-typed render path there. Documenter.jl uses **`ANSIColoredPrinters.jl`** to convert captured ANSI output into HTML for documentation builds — the canonical bridge from terminal-coloured `@example` output to rendered HTML.
Recommended libraries
- printstyled (Base)
Julia stdlib's canonical styled-print function — `printstyled([io], xs...; color=:red, bold=true, italic=true, underline=true, blink=true, reverse=true, hidden=true)`. `color` accepts a named `Symbol` (`:red` / `:light_blue` / `:cyan`), an integer 0–255 for the 256-colour palette, or a 3-tuple `(r, g, b)` for truecolor. The `Base.text_colors` and `Base.disable_text_style` dicts expose the underlying SGR codes for hand-rolled output. No extra dependency.
- Crayons.jl
De-facto third-party Julia ANSI library. `Crayon(foreground = :red, background = :light_blue, bold = true, underline = true)` constructs a reusable style; `print(c, "text")` applies it; `Crayon(reset = true)` returns to defaults. Crayons compose via `*` (`bold_red = Crayon(foreground=:red, bold=true) * Crayon(underline=true)`). `Box(content; foreground = :green)` wraps a value with start + reset bytes automatically. Auto-disables when `Base.have_color` is false.
- Term.jl
Modern Julia TUI library — `Panel`, `Tree`, `ProgressBar`, syntax-highlighted code printing (`@green "text"`-style markup macros), table layouts, `tprint("[red bold]error[/red bold] denied")` markup parser, and ANSI-aware multi-line layout primitives. The right call when `printstyled` + `Crayons` aren't enough and you need full panels.
- REPL.TerminalMenus (stdlib)
Julia stdlib interactive-menu module — `RadioMenu(options; pagesize=N)` single-select, `MultiSelectMenu(options; pagesize=N)` multi-select, arrow-key navigation, Enter to confirm, q or Ctrl-C to cancel. Used by Pkg.jl itself for interactive package selection. Sub-class `AbstractMenu` for custom widgets. No extra dependency.
Idiomatic patterns
# Julia supports \e (backslash-e), \x1b (hex), \033 (octal),
# and \u1b (Unicode) in string literals — all parse to byte 27.
# \e reads cleanest; pick whichever you prefer.
println("\e[1;31merror:\e[0m permission denied")
println("\e[33mwarn:\e[0m deprecated flag")
println("\e[32mok:\e[0m 142 tests passed")
# Truecolor — 38;2;R;G;B
println("\e[38;2;255;128;0morange truecolor\e[0m")
# Idiomatic Julia: printstyled wraps the bytes for you.
printstyled("error: "; color = :red, bold = true)
println("permission denied")
printstyled("warn: "; color = :yellow); println("deprecated flag")
printstyled("ok: "; color = :green); println("142 tests passed")
# 256-colour — integer 0-255:
printstyled("orange (256-palette)\n"; color = 208)
# Truecolor — (r, g, b) tuple:
printstyled("orange (truecolor)\n"; color = (255, 128, 0))
# Underline + bold + reverse all compose as keyword args:
printstyled("FATAL\n"; color = :red, bold = true, reverse = true)# import Pkg; Pkg.add("Crayons")
using Crayons
# A Crayon is a reusable style; print(c, "x") applies it,
# print(Crayon(reset = true), ...) returns to default.
red = Crayon(foreground = :red, bold = true)
print(red, "error: ")
println(Crayon(reset = true), "permission denied")
# Compose via * — combine attributes into a single Crayon:
heading = Crayon(foreground = :blue, bold = true) * Crayon(underline = true)
println(heading, "Section 1", Crayon(reset = true))
# Box wraps content with start + reset bytes automatically:
println(Box("warm orange"; foreground = (255, 128, 0)))
# 256-palette — pass an Int 0-255:
println(Crayon(foreground = 208), "orange256", Crayon(reset = true))
# Background colour (named symbol):
println(
Crayon(background = :yellow, foreground = :black),
" CAUTION ",
Crayon(reset = true),
" brakes wet",
)
# String-interpolation friendly — Crayons stringify cleanly:
status = string(Crayon(foreground = :green), "PASS", Crayon(reset = true))
println("build: $(status)")# Base.have_color is set at Julia startup from --color=:
# --color=auto (default): true when stdout is a TTY AND
# $NO_COLOR is unset
# --color=yes / true: always true (force-enable)
# --color=no / false: always false (force-disable)
#
# Julia handles automatically:
# - Piping stdout to a file → have_color = false
# - $NO_COLOR set → have_color = false
#
# CRUCIAL Jupyter / Pluto caveat: have_color = false in
# notebook front-ends — ANSI passes through as raw text.
# Switch to HTML / MIME-typed display there:
#
# display("text/html", "<span style='color:red'>err</span>")
function styled(text::AbstractString, sgr::AbstractString)
Base.have_color || return text
return "\e[$(sgr)m$(text)\e[0m"
end
# Roll your own check when you don't want to depend on the
# startup flag — useful in libraries that may run under
# IOContext wrappers that reset have_color:
function ansi_capable(io::IO = stdout)
haskey(ENV, "NO_COLOR") && return false
return get(io, :color, false) === true || isa(io, Base.TTY)
end
println(styled("OK", "32"))
println(styled("FAIL", "1;31"))
# Force-enable in scripts where capture wrapper resets have_color
# (e.g. tests with IOBuffer):
let buf = IOBuffer()
ctx = IOContext(buf, :color => true)
printstyled(ctx, "captured red\n"; color = :red)
println(String(take!(buf))) # contains real \e[31m bytes
end
# To force colour on at startup: julia --color=yes script.jl# stdlib — no Pkg.add needed.
using REPL.TerminalMenus
# Single-select arrow-key menu — same API Pkg.jl uses.
choice = request(
"Pick a build profile:",
RadioMenu(
["debug", "release", "release-with-debug-info"];
pagesize = 5,
),
)
println("selected: ", choice) # 1-based index; -1 if cancelled
# Multi-select: SPACE toggles, ENTER confirms, q/Ctrl-C cancels.
picks = request(
"Pick test groups (space to toggle, enter to confirm):",
MultiSelectMenu(
["unit", "integration", "e2e", "perf"];
pagesize = 5,
),
)
println("selected: ", picks) # Set{Int}, 1-based indices
# Menus emit raw ANSI — they work in every TTY-attached
# Julia REPL but NOT under IJulia / Pluto (no raw-mode stdin).
# Guard with isinteractive() + isa(stdin, Base.TTY) before
# calling request() in library code.