ANSI escape codes by language
Recommended libraries and idiomatic patterns for emitting ANSI / VT escape sequences from each major programming language. Every snippet maps back to a documented sequence — follow the links to see byte forms and terminal support.
Helpers by language
Systems
- Go
Go's
fmt.Print*family is byte-clean —\x1b[31mjust works on Unix and on Windows 10+ (whenENABLE_VIRTUAL_TERMINAL_PROCESSINGis set, whichgolang.org/x/sys/windowscan flip on). For anything more than a handful of escapes, reach forfatih/coloror, for full TUIs, the Charmbracelet stack (lipgloss+bubbletea). - C
C has no string-escape for ESC — write
\x1b(hex) or\033(octal).printfandfputsare byte-clean on every platform; on Windows 10+ enable virtual terminal processing viaSetConsoleMode(..., ENABLE_VIRTUAL_TERMINAL_PROCESSING)first. For anything resembling a TUI (menus, windows, mouse) linkncursesrather than hand-rolling. - Swift
Swift's string literal uses
\u{1b}for the ESC byte — not\x1b(which is the #1 friction point copy-pasting C / Rust / Python recipes into Swift).print("\u{1b}[31mred\u{1b}[0m")works on macOS Terminal, iTerm2, and on Linux when targeting swift-on-server. For ergonomic colour APIs reach forRainbow(the most-starred Swift ANSI string library), which addsStringextensions like"error".red.bold. Apple's ownswift-argument-parserships coloured--helpoutput via the same primitives. For full TUI work (panels, mouse, raw mode)TermKitis the ncurses-style option. Capability gating usesisatty(STDOUT_FILENO)fromDarwin/Glibc, orFileHandle.standardOutput.isTerminalon Swift 5.7+ where it's bridged. - Rust
Rust string literals support
\x1bdirectly —println!("\x1b[31mred\x1b[0m")works with no dependencies. For ergonomic colour APIs reach forowo-colors(zero-allocation, compile-time) orcolored(more dynamic). For NO_COLOR / isatty handling without writing the env-check yourself,termcolormirrors the Go pattern. For full TUI work — windows, cursor, raw mode, mouse — usecrossterm; for higher-level layouts,ratatui(formerly tui-rs) sits on top of it. - Crystal
Crystal has the most ergonomic stdlib ANSI of any modern language. The
"..."string literal accepts every common ESC form —"\e","\033"(octal),"\x1b"(hex),"\u001b"(4-digit Unicode), and"\u{1b}"(variable-width Unicode) — pick the one your team reads fastest.putsandprintwrite raw bytes (puts "\e[1;31merror:\e[0m permission denied"works on a freshly-installed compiler with no shards). On macOS, Linux, BSDs, and Windows 10+ Conhost 1709+ the terminal parses the bytes natively. Reach for the stdlibColorizemodule (require "colorize") first — it ships with the compiler and the API reads like English:"error".colorize.red.bold,"warning".colorize(:yellow),"hot".colorize.fore(255, 80, 0)for truecolor,"x".colorize.fore(196)for 256-palette. Colorize auto-checksSTDOUT.tty?on first use and silently no-ops when redirected to a file or pipe — you get the right behaviour with zero capability boilerplate. For cursor movement / clear / scroll / save-restore reach for theterm-cursorshard (Term::Cursor.move(0, 0),Term::Cursor.clear_screen,Term::Cursor.save). For TTY size — Crystal's stdlib intentionally omits a public terminal-size API — pull in theterm-screenshard (Term::Screen.size # => {height, width}). For a full blessed-equivalent TUI runtime (windows, focus, mouse, redraw loop) reach forcrysterm, the most mature Crystal TUI library. Capability gating:STDOUT.tty?is the stdlib check — returnsfalsewhen stdout is redirected to a file, pipe, or non-terminal IO. Pair withENV["NO_COLOR"]?for the universal env-var convention;Colorize.enabled = falseto force-disable globally. Windows VT note: Crystal compiles to native binaries — on pre-Conhost-1709 Windows builds the terminal won't parse ANSI bytes until you callLibC.SetConsoleModewithENABLE_VIRTUAL_TERMINAL_PROCESSING. Crystal's compile-time{% if flag?(:win32) %}macro is the idiomatic gate: the libc bindings only get compiled on Windows targets, so a single source file builds cleanly on every supported platform. - Nim
Nim's stdlib
std/terminalis the most comprehensive batteries-included terminal module of any compiled language — a single import gives you SGR colour (setForegroundColor(fgRed)), text style (setStyle({styleBright, styleUnderscore})), 256-palette viaColor(...), truecolor (with an explicitenableTrueColors()opt-in), cursor positioning, screen erase, terminal-size queries, raw-mode toggle, and isatty all in one place. For most apps the dependency list ends right there. String-literal forms: Nim accepts"\e"(ESC named escape),"\x1b"(hex, exactly 2 digits),"\u001b"(4-digit Unicode), and"\u{1b}"(variable-width Unicode). The Nim twist — and the page's primary SERP differentiator — is that"\NNN"is decimal, not octal like every C-family language."\27"in Nim is byte 27 (ESC); the same literal in C, Crystal, Python, or Ruby is byte\002followed by the character'7'. If you're porting ANSI code from another language and"\033[31m"is throwing a parse error, the fix is"\27[31m"(decimal) or"\x1b[31m"(hex) or — most readably —"\e[31m". For everything beyondstd/terminalreach for:std/colors(stdlib colour-name table —colWhite,colAliceBlue, full HTML/CSS palette asColorconstants, paired withenableTrueColors()to emit38;2;R;G;Bfrom the named constant);chronicles(status-im's structured logger — the canonical Nim logging library, ships ANSI colour topic / level / timestamp formatting out of the box, used by every Nim Ethereum/blockchain stack);illwill(johnnovak's curses-free TUI library — the modern Nim answer to ncurses, ships its own ANSI back end so it composes cleanly withstd/terminalfor non-fullscreen output). Capability gating:isatty(stdout)fromstd/terminalreturnsfalsewhen stdout is redirected. Pair withgetEnv("NO_COLOR")for the universal opt-out andgetEnv("FORCE_COLOR")for opt-in. Windows VT mode:std/terminalcallsSetConsoleModewithENABLE_VIRTUAL_TERMINAL_PROCESSINGautomatically on first use of any colour helper, so you don't need awhen defined(windows)gate around your colour calls — Nim handles it. Pre-Windows-10-1709 builds (Conhost without VT support) fall back to direct Win32 console-API colour calls, transparent to your code. - Zig
Zig's standard library has no colour helper, but byte literals are first-class —
"\x1b[31m"is a*const [5:0]u8and writes throughstd.io.getStdOut().writer().print(orstd.debug.printfor quick prototyping) without any encoding work. Since Zig 0.11 the canonical capability gate isstd.io.tty.detectConfig(std.io.getStdOut())— it returns a tagged union (.no_color,.escape_codes,.windows_api) that honoursNO_COLOR, checksisatty(2), and selects the right backend on Windows pre-VT Conhost. Wrap your write sites inswitch (config) { ... }and emit raw escapes only on.escape_codes. For more than ad-hoc prints: libvaxis (rockorager/libvaxis) is the modern full-screen TUI library — pure Zig, uses the kitty keyboard protocol when available, ships its own rendering pipeline. mibu (xyaman/mibu) is the small, focused ANSI helper for non-fullscreen output (mibu.color.print(.{ .fg = .red }, "error", .{})). zig-spoon is a minimal terminal-control library if you want raw-mode + cursor positioning without a full TUI framework. All three sit on the same byte forms documented here.
Scripting
- Python
Python's stdlib has no built-in colour helper, but every byte you would write by hand is fine — strings can carry
\x1bescapes directly. For cross-platform support (Windowscmd.exebefore 1909 didn't parse ANSI by default), reach forcolorama. For richer TUIs,richandprompt_toolkitbuild on the same byte forms documented here. - PowerShell
PowerShell 7.2+ ships
$PSStyle, a typed automatic variable that is arguably the most modern stdlib colour API across all languages — capability-gated via$PSStyle.OutputRendering(Ansi/PlainText/Host), enumerates every SGR code as named properties ($PSStyle.Foreground.Red,$PSStyle.Bold,$PSStyle.Reset), and integrates with the host so a pipedGet-ChildItemstrips colour automatically. For PS 5.1 (Windows-default) you fall back toWrite-Host -ForegroundColoror raw VT via the `e `escape token (PS 7+) /[char]27(PS 5.1). Windows 10+ Conhost / Windows Terminal both parse ANSI natively — nocolorama`-style shim needed. - Bash / shell
Bash has two reliable ways to emit escape bytes:
printfwith\033/\x1b(POSIX-portable), andtput(terminfo-driven, the safest because it adapts to$TERM).echo -eworks in bash but breaks in dash / sh; avoid it in portable scripts. The literal$'\e[...'ANSI-C quoting form is bash/zsh-only but very readable. - Ruby
Ruby's double-quoted strings accept
\eas the ESC literal (no need for\x1bor\033), andputs/print/STDOUT.writeare byte-clean on every platform. Ruby 3.0+ on Windows auto-enablesENABLE_VIRTUAL_TERMINAL_PROCESSINGon the standard streams, so\e[31mlights up Conhost and Windows Terminal without acolorama-style shim. For ergonomic styling:colorizemonkey-patchesStringwith chainable colour methods ("oops".red.bold),term-ansicolor(Florian Frank's classic — powerspryandrspec) offers both module-call (Term::ANSIColor.red(...)) and mixin styles, andpastel(from the TTY toolkit) ships isolated SGR helpers that auto-detectNO_COLORandCLICOLOR_FORCEwithout pollutingString. For full TUIs reach for the rest of the TTY toolkit —tty-prompt,tty-spinner,tty-cursor,tty-progressbar. - Perl
Perl supports the
\eescape in double-quoted strings — the most ergonomic ESC literal across all the ANSI-emitting languages this site documents.print "\e[31mred\e[0m\n"works on every Perl back to 5.000 (released 1994), on macOS, Linux, BSDs, and modern Windows (Windows Terminal / Conhost 1709+). For the canonical helper reach forTerm::ANSIColor, a Perl core module bundled with the interpreter since 5.6 —color("bold red")returns the escape string,colored(["bold red"], "text")wraps text with auto-reset, andcolorstrip("\e[31mred\e[0m")removes ANSI cleanly (a popular long-tail SERP target). Capability gating uses Perl's idiomatic-t STDOUTfile test (returns true on a TTY),IO::Interactive::Tiny::is_interactivefor a portable wrapper, and$ENV{NO_COLOR}for the cross-language kill switch. For full TUIs linkCurses::UI(Perl binding for ncurses); for cap-database lookups,Term::Capqueries terminfo. Perl is still the canonical glue for sysadmin one-liners and log-processing pipelines — thecolorstriprecipe alone serves a large "how do I strip ANSI from a log file" search demand that lands on Stack Overflow Perl one-liners. - R
R supports
\033(octal) directly in double-quoted strings — the most portable ESC literal across every R version since the language's birth.cat("\033[31mred\033[0m\n")works on every interpreter, on macOS, Linux, BSDs, and modern Windows (Windows Terminal / Conhost 1709+ parses ANSI natively). Modern R (≥ 4.0) also accepts"\u{1b}"Unicode and"\x1b"hex (strict 2-digit) escapes. For the canonical helper reach forcrayon— the de-facto R ANSI library used by tidyverse, testthat, pkgdown, devtools:crayon::red("error")returns a styled string,crayon::boldand friends compose freely (bold(red(...))), andmake_style("#ff8000")factory-builds a truecolor styler. The modern hadley/r-lib companioncliadds semantic helpers (cli_alert_danger(),cli_h1(), pluralized inline markup) on top of crayon's primitives.gluetemplates the same vocabulary inglue_col("{red error}"). For ANSI-aware string operations (the canonical R log scrubber lives here),fansi::strip_sgrremoves every CSI sequence cleanly, andfansi::nchar_ctl/substr_ctlcount display width and slice without corrupting bytes. Capability gating in R:crayon::has_color()is the authoritative call — it already respectsNO_COLOR,isatty(stdout()), thecrayon.enabledoption, and the RStudio Source-pane vs Console divergence (the #1 "why does my crayon output show raw escapes" SERP click —source()runs go through a different sink that strips ANSI; the Console pane parses it correctly). knitr / rmarkdown rendering also disables colour by default to keep the rendered output portable. - Lua
Lua has no
\eescape literal, but\27(decimal) works in every interpreter back to Lua 5.1, and\x1b(hex) works since Lua 5.2.io.writeandprintare byte-clean on PUC-Rio Lua (5.1 / 5.2 / 5.3 / 5.4), LuaJIT, and the embedded interpreters that ship with Neovim, OpenResty, Redis, and Wireshark. Beware: long-bracket strings[[...]]do not expand escape sequences — only regular"..."strings do, so always use double-quoted string literals when emitting ANSI bytes. For ergonomic styling: ansicolors.lua (kikito's classic) parses pattern strings likecolors('%{red bold}error:%{reset} permission denied')into SGR sequences — a single 1-file dependency, installable via LuaRocks or vendor-copy. term.lua (hoelzro's port of kikito's terminal-control helpers) covers the non-SGR side:term.clear(),term.cleareol(),term.cursor.goto(x, y),term.cursor.hide(), alt-screen + cursor save/restore. For full-screen TUIs link lcurses (Lua binding for ncurses). For capability gating —isatty(),TIOCGWINSZ, raw-mode termios — use LuaPosix.
Web
- JavaScript / Node.js
Node's
process.stdout.writeandconsole.logare both byte-clean. The browser console (DevTools) accepts a CSS-styled subset of escapes via%c, NOT ANSI bytes — those only render in real terminals. For ergonomics,chalkis the canonical choice;picocolorsis the same idea in ~100x less code. - TypeScript (Deno / Bun / Node)
TypeScript itself transpiles away — the runtime bytes are whatever JavaScript would emit. The interesting difference between the three TS runtimes is the stdlib write call: Node uses
process.stdout.write, Deno usesDeno.stdout.writeSync(orconsole.log), Bun accepts both Node'sprocess.stdoutshim and its ownBun.write(Bun.stdout, …). All three are byte-clean. For typed ergonomics reach forpicocolors(zero-dep, works in every runtime vianpm:/jsr:/ direct import), Deno's bundled@std/fmt/colors, orkoloristfor a tiny isomorphic option.chalk5+ is ESM-only and works under Node / Bun /npm:chalkin Deno. - Node.js (CLI tools)
Node-CLI surfaces where
/use/javascriptonly sketches:process.stdout.isTTYfor capability detection (false when piped, redirected, or running under CI),process.stdout.columnsfor adaptive width, the ESM-only chalk 5+ break that bites every CommonJS project, and thenode:tty+readlineANSI key-decoding primitives. If you're shipping a CLI binary (npx,bin/entry, oclif, commander), this is the page; if you're picking a colour library for a Next.js / Vite app, the JavaScript page is more general. - PHP
PHP CLI mode (
php -r,php script.php) is byte-clean on STDOUT —echo "\e[31mred\e[0m"works since PHP 5.4 (the version that introduced\ein double-quoted strings; older code uses"\x1b"orchr(27)). On Windows, Conhost since Windows 10 1709 parses VT natively, but PHP recommends explicitly flipping the mode withsapi_windows_vt100_support(STDOUT, true)(available since PHP 7.2) so behaviour is uniform across hosts and CGI fallbacks. For more than ad-hoc echoes reach for Symfony Console — the canonical PHP CLI framework that powers Composer, Laravelartisan, Symfonyconsole, and Drupaldrush. ItsOutputFormatteruses XML-like tags (<info>...</info>,<fg=red;bg=white;options=bold>...</>) that compile to ANSI under the hood and auto-strip when the stream is non-TTY. Termwind ships a Tailwind-CSS-for-the-terminal layer (class names liketext-red-500,bg-blue-500,font-bold) and underpins PHPStan, Pest, Laravel Zero. laravel/prompts offers modern interactive prompts that work standalone outside Laravel. - Dart
Dart's
dart:iostdoutalready exposes the canonical capability check most other ecosystems force you to roll yourself —stdout.supportsAnsiEscapesreturnstrueon a TTY that understands VT sequences,falsewhen piped, redirected, or running on a legacy Conhost without VT enabled. Pair it with rawstdout.write('\x1B[31m…\x1B[0m')(Dart accepts both\x1Band\u001B) and you have a portable starting point with zero dependencies on macOS, Linux, and Windows Terminal / Conhost 1709+. For ergonomic colour APIs reach forchalkdart(the most popular Dart ANSI library — chained-extension API mirrored after Node'schalk),tint(compact, modern), orcli_util(Google's first-party CLI helpers including an ANSI module). The Flutter SDK's ownflutterCLI uses these primitives — its progress / build-status output is a canonical reference for Dart-shaped capability gating.
JVM
- Java
Java string literals have no
\eescape — write\033(octal) or\u001b(Unicode escape).System.out.println("\033[31mred\033[0m")is byte-clean on every JVM. On Windows, Conhost since Windows 10 1709 parses VT natively, but for compatibility with older Conhost (and to handle the Maven / Gradle / IntelliJ output-wrapping that often strips ANSI), callAnsiConsole.systemInstall()from Jansi — it swapsSystem.outandSystem.errwith wrappers that translate ANSI to Win32 console-API calls on legacy hosts and pass through on modern ones. For more than ad-hoc print statements: JLine 3 is Java'sreadline(raw mode, line editing, history, completion, masked password, terminal capability detection) — the foundation under Spring Shell, JBang, Groovy'sgroovysh, and OpenJDK'sjshell. picocli annotation-drives CLI arg parsing with ANSI-aware help formatters viaHelp.Ansi.AUTO(honoursNO_COLORandisatty). For full-screen TUIs reach for Lanterna — a Swing-style hierarchy ofWindow/Panel/Componentrendered to ANSI. - Kotlin
Kotlin string literals use
\u001Bfor the ESC byte — Kotlin has no\xhex escape, only\u####Unicode escapes (same as Java).println("\u001B[31mred\u001B[0m")is byte-clean on macOS / Linux and on Windows 10+ (where stdout enables VT by default). For ergonomic colour + tables + progress + markdown rendering reach formordant, the most popular Kotlin ANSI library — it auto-detects terminal capability, respectsNO_COLOR, and downgrades cleanly when piped. For full interactive TUIskotterprovides a coroutine-friendly DSL.picocli(a JVM CLI argument parser) ships built-in ANSI colour support for--helpand is the natural choice when you also need CLI parsing. Gradle / IDE caveat: IntelliJ's Run console enables ANSI, but the Gradle daemon strips colour by default — pass--console=plain(or setorg.gradle.console=plainingradle.properties) to see escape bytes verbatim. Android Logcat does not interpret ANSI; usemordant'sTheme.PlainAsciior capability-detect when targeting Android. - Scala
Scala's string literals do not support
\eor\x1b— the same constraint as Java and Kotlin. The portable spelling is the Unicode escape\u001B, which works on Scala 2 (since 2.0) and Scala 3 alike. Scala 2 additionally accepted octal\033, but Scala 3 removed octal escapes from string literals, so write"\u001B[31m"if you want code that compiles on every modern Scala.println("\u001B[1;31merror:\u001B[0m permission denied")works on every JVM ≥ 8, on macOS, Linux, BSDs, and on Windows 10+ with Conhost 1709+ / Windows Terminal (both parse ANSI natively). For the idiomatic helper reach forfansi(com.lihaoyi::fansi) — Li Haoyi's canonical Scala ANSI library used by Ammonite, mill, Scalafix, scalafmt, and most of the modern Scala ecosystem.fansi.Color.Red("error")returns afansi.Strvalue with structured colour spans rather than embedded bytes;++concatenates spans,.overlay(fansi.Bold.On, 0, 5)overlays attributes on a sub-range,.rendermaterialises the byte string.scala.io.AnsiColoris the stdlib mixin —Console.RED + "error" + Console.RESET-style constants for one-off bytes without a library dependency.pprint(also Li Haoyi) is the canonical coloured pretty-printer —pprintln(value)emits indented, fansi-coloured output, and the Ammonite REPL ships it as the defaultprintlnreplacement. For richer terminals (line editing, raw mode, capability detection) reach for JLine 3 — the JVM terminal abstraction Scala's REPL itself uses. Capability gating:System.console() != nullis the JVM-portable check (returnsnullwhen stdin/stdout is redirected). Pair withsys.env.get("NO_COLOR")for the env-var convention. SBT caveat: SBT wraps the build's stdout to inject its own progress UI, so child-process ANSI output sometimes loses colour — pass-Dsbt.color=alwayson the SBT command line, or setThisBuild / outputStrategy := Some(StdoutOutput)inbuild.sbt, when launching scripts that need true colour passthrough.scala-cliandmilldon't share this quirk. - Clojure
Clojure inherits Java's string-literal rules — only the Unicode escape
\u####works.\eis not supported,\x1bis not supported,\033is 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 forclansi— 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 forio.aviso/pretty— the library Leiningen itself uses for its own coloured error formatting;io.aviso.ansi/red,io.aviso.ansi/bold-red, plusio.aviso.exception/format-exceptionfor pretty stack traces. For coloured pretty-printing of data structures reach formvxcvi/puget— the canonical Clojure pretty-printer, used byciderfor 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 usejansivia Java interop —(org.fusesource.jansi.AnsiConsole/systemInstall)enables VT on every JVM-capable platform. Capability gating:(some? (System/console))is the JVM-portable check (returnsnilwhen 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:valuefield and render it themselves, not by piping raw stdout. ANSI escapes inside the:valuestring 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 installcider-nrepl'sprint-colormiddleware that maps SGR codes to editor face overlays.
Functional
- Haskell
Haskell's string literals support
"\ESC"— the canonical, readable, control-character-named ESC escape. It parses identically to"\27"(decimal) and"\x1B"(hex), but\ESCis the Haskell-idiomatic form (distinct from every other language this site documents — Haskell is the only mainstream language where the literal\ESCkeyword renders to byte 27).putStr "\ESC[31mred\ESC[0m\n"works on every GHC since 7.0, on macOS, Linux, BSDs, and on Windows 10+ with Conhost 1709+ / Windows Terminal (both parse ANSI natively). For older Windows builds (pre-1607 Conhost) theansi-terminallibrary hides theSetConsoleModecall behindhSupportsANSI. For the canonical helper reach foransi-terminal— declarativesetSGR [SetColor Foreground Vivid Red, SetConsoleIntensity BoldIntensity]API with explicitResetclarity, cross-platform Windows VT enabling, named cursor / erase / scroll helpers, and a Type-Safe Way that catches bad combinations at compile time.rainbowlayers a fluentText/Stringcolouring DSL on top —chunk "error" & fore red & boldbuilds aChunkandputChunkLnrenders it. For full TUIs linkbrick(vty-based, declarative panel-and-event-loop architecture — used bylorri,matterhorn, theghcidTUI,tasty-bench-tui). For REPL-style line editing (history, completion, multi-line) reach forhaskeline— the readline-equivalent that GHCi itself uses. Capability gating:ansi-terminal'shSupportsANSI stdoutis the canonical capability check (handles macOS/Linux/BSD isatty + Windows VT enablement); pair it withlookupEnv "NO_COLOR"(System.Environment) andhIsTerminalDevice stdout(System.IO) when you want to skip the library dep. The GHCi REPL parses ANSI in modern terminal hosts;cabal replandstack ghcisometimes wrap stdout — known friction. - Julia
Julia's string literals support
\edirectly — 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.coloraccepts a namedSymbol(:red/:light_blue/:cyan), an integer 0–255 for the 256-palette, or a 3-tuple(r, g, b)for truecolor on capable terminals. TheBase.text_colorsandBase.disable_text_styledicts expose the underlying SGR codes for hand-rolled output. For composable string-level styling reach forCrayons.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)), andBox(content; foreground = :green)wraps a value with start + reset bytes around it. For full TUIs linkTerm.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 stdlibREPL.TerminalMenusmodule shipsRadioMenu/MultiSelectMenu— used by Pkg.jl itself for interactive package selection. Capability gating:Base.have_coloris the canonical flag — Julia sets it at startup from--color=auto|yes|no(autoistruewhen stdout is a TTY and$NO_COLORis unset). Piping Julia output toless -Rworks as expected. Crucial caveat: under Jupyter / IJulia / Pluto notebook front-endsBase.have_colorisfalseand ANSI escapes pass through as raw text — switch to an HTML or MIME-typed render path there. Documenter.jl usesANSIColoredPrinters.jlto convert captured ANSI output into HTML for documentation builds — the canonical bridge from terminal-coloured@exampleoutput to rendered HTML. - OCaml
OCaml string literals support
"\027"(decimal — the canonical OCaml ESC form, works on every OCaml back to 1.0),"\x1b"(hex, OCaml 4.06+), and"\o033"(octal, OCaml 4.06+). The OCaml twist:"\NNN"is decimal, not octal like C — same trap Nim users hit."\033"in C is byte 27 (octal);"\033"in OCaml is byte 33 (decimal — the!character). When porting from C the canonical fix is"\027"(decimal) or — on OCaml 4.06+ —"\x1b"(hex) or"\o033"(octal, when you want to keep the literal-looking-the-same). There is no\enamed escape.print_string "\027[1;31merror:\027[0m permission denied\n"writes raw bytes through the runtime'sstdoutchannel, parsed natively on macOS, Linux, BSDs, and Windows 10+ Conhost 1709+. Reach forANSITerminal(opam package, the canonical OCaml ANSI library) —ANSITerminal.print_string [red; Bold] "error: "reads as a list of styles followed by the text, auto-emits the reset, and handles capability detection on Windows via native console-mode toggling. For structured / formatted output reach forfmt(the Dune-default formatter) —Fmt.pr "@{<red>error@}: %s@." msgparses semantic markup tags into SGR sequences and respectsFmt.set_style_renderer Format.std_formatterAnsi_tty. Pairfmtwith **logs** for structured logging with per-source-level coloured prefixes (Logs.info (fun m -> m "started on %d" port)produces a coloured[INFO]prefix when stdout is a TTY). For a full TUI runtime reach for **notty** — pqwy's high-quality declarative TUI library with image composition (I.string A.(fg red) "error"returns anNotty.imageyou compose with<|>/<->and render viaNotty_unix.Term`). Capability gating:Unix.isatty Unix.stdout(theunixlibrary, ships with most OCaml installs) returnsfalsewhen stdout is redirected. Pair withSys.getenv_opt "NO_COLOR"for the universal opt-out. utop REPL caveat (page's primary SERP differentiator):print_string "\027[31mfoo\027[0m"inside utop DOES render red (utop pipes stdout to its underlying lambdaterm which parses ANSI). However, when the REPL displays the value of a top-level binding (let s = "\027[31mfoo\027[0m";;), utop renders the OCaml string literal back to you — escape bytes appear verbatim as\027[...], not as a colour. The fix: print the string explicitly (let () = print_string s) rather than letting utop echo the value, or installutop-fulland useUTop.set_phrase_terminator/UTop.set_show_boxto customise REPL rendering. - Erlang
Erlang string literals accept the widest set of ESC forms of any language documented here —
"\e"(named ESC),"\x1b"(hex),"\033"(octal — Erlang's\NNNIS octal, same as C), AND"\^["(caret-notation control character —^Athrough^Zmap to ASCII 1..26 and^[maps to ASCII 27 = ESC). Strings in Erlang are charlists (lists of integers); binaries<<"...">>are first-class too.io:format("\e[1;31merror:\e[0m ~s~n", [Msg])is the canonical Erlang one-liner —~sinterpolates the second-argument list,~nis the platform newline, and ANSI bytes pass throughio:format/2unchanged to the calling process'sgroup_leader(which for shell-attached code is the user's terminal). For SGR helpers reach forcf(project-fifo/cf — "Erlang Colour Format") — extendsio:format's~directive syntax with~!r(red),~!g(green),~!b(blue),~!_(bold),~!!(reset), etc.cf:format("~!rerror:~!! permission denied~n")reads more concisely than the raw-byte form. For structured logging reach for OTP's stdlibloggermodule (built into OTP 21+ — Mar 2018) —logger:info("started", #{port => 8080})produces a coloured per-level prefix when the default handler is on a TTY, JSON on redirect. For the older pre-OTP-21 codebases you still see in production reach forlager(basho's structured logger — the canonical pre-logger choice, ANSI-coloured per-level prefixes, sink-based per-app filtering). The OTP release-mode caveat (page's primary SERP differentiator): every Erlang process has agroup_leaderthat handles its I/O. In an interactive shell or escript,group_leaderIS the user's terminal — ANSI passes through. When you build an OTP release (rebar3 release→bin/myapp daemonorforeground), thegroup_leaderis reassigned: indaemonmode it's a logger process that writes tolog/erlang.log.N, and ANSI bytes appear literally in the log file as\e[31m...text. The fix: use theloggermodule's coloured handler (which strips ANSI when its output destination is not a TTY — auto-detection viaio:rows()returning{error, enotsup}), or detect viaapplication:get_env(kernel, standard_io_handler)and gate yourio:formatcolour calls explicitly. Capability gating:io:rows()returns{ok, Rows}when stdout is a TTY,{error, enotsup}when redirected. Pair withos:getenv("NO_COLOR")for the universal opt-out. Modern Erlang shell (erl, OTP 25+) parses ANSI inio:formatoutput natively on all platforms including Windows (the BEAM emulator handlesSetConsoleModeon first ANSI byte). - Elixir
Elixir ships an
IO.ANSImodule in the standard library — every named SGR and a handful of cursor / screen helpers are functions that return their byte form (IO.ANSI.red()returns"\e[31m"). The idiomatic path isIO.ANSI.format/2, which takes a list mixing atoms (the named escapes) and binaries (your text) and returns an iolist suitable forIO.puts/1orIO.write/1— e.g.IO.puts(IO.ANSI.format([:bright, :red, "error: ", :reset, "permission denied"])).IO.ANSI.enabled?/0is the capability gate baked into stdlib: it honours the:elixirapp env:ansi_enabled(set byiexto true on a tty, false on a pipe) plus theNO_COLORenv var. For more than ad-hoc prints: Bunt (savonarola/bunt) sugarsIO.ANSIwith named colour tags inside the string itself —Bunt.puts([:red, "error: ", :default_color, "permission denied"])or the inline formBunt.puts("[red]error:[/red] permission denied"). Owl (fuelen/owl) is the modern full-output toolkit (boxes, tables, progress bars, multi-line spinners, ANSI links) — used by Phoenix-generated CLI tasks,mixextensions, andlivebookstartup output. For full-screen TUIs reach for Ratatouille (ndreynolds/ratatouille) — an Elm-stylemodel / update / viewarchitecture rendered to ANSI via termbox2.
.NET
- C#
C# string literals support
\x1bdirectly —Console.Write("\x1b[31merror\x1b[0m")is byte-clean on macOS / Linux and on Windows 10 1709+ / Windows Terminal / Windows 11, where Conhost parses VT by default. For anything richer than ad-hoc escapes,Spectre.Consoleis the de facto modern .NET TUI / styled-console library (markup syntax[red]error[/], tables, progress, prompts) andPastelgives you a one-liner extension method ("hello".Pastel(Color.Red)). On legacy Windows builds before 1607 you must flipENABLE_VIRTUAL_TERMINAL_PROCESSINGvia aSetConsoleModeP/Invoke before the first write. SetConsole.OutputEncoding = Encoding.UTF8to keep box-drawing and emoji intact. - F#
F# string literals accept
\x1bdirectly (since F# 4.5, August 2018),\u001b(Unicode codepoint — works in every F# version including .NET Framework F# 2.0), and\NNNtriplets — but F#\NNNis DECIMAL, not octal (the .NET runtime quirk shared with C# / VB.NET / PowerShell, and the opposite of C / Erlang / Java where\033is octal 27). The portability trap: copy"\033[31m"from a C / Erlang / Bash snippet into F# and you get the BYTE 33 (=!), not ESC. Use"\x1b"(F# 4.5+) or"\027"(decimal triple, all versions) or"\u001b"(Unicode codepoint, all versions) — never"\033". The canonical F# one-liner isprintfn "\x1b[1;31merror:\x1b[0m %s" msg—printf/printfnis F#'s type-checked printf family (the format string is parsed at COMPILE time, unlike C# / Java where format strings are validated at runtime).%sinterpolates a string,%dan int,%Aany value via structured pretty-print. Escape bytes embedded in the format string pass through toSystem.Console.Outunchanged. Theprintfnnewline is platform-aware (\r\non Windows,\non Unix) — use plainprintfif you're building a single line incrementally. For anything richer than ad-hoc escapes,Spectre.Consoleis the de facto modern .NET console toolkit — works identically from F# (AnsiConsole.MarkupLine "[bold red]error:[/] permission denied"). Pair it withArgu(F#'s canonical CLI parser — auto-coloured--helpoutput, type-safe argument records via discriminated unions) for full F# script ergonomics. For F#-idiomatic templated coloured output reach forBlackFox.ColoredPrintf—cprintfn "$red[error:] %s" msguses$colour[...]template syntax that compiles to standard ANSI. For F# build scripts (FAKE) theFake.Core.Tracemodule emits colourised step headers, success / failure markers, and structured logging native to the FAKE DSL. The F# Interactive (FSI) REPL caveat (page's primary SERP differentiator):dotnet fsifrom a normal terminal prompt parses ANSI inprintfnoutput — colours work as expected. BUT inside Visual Studio's *F# Interactive* tool window AND inside the VS Code Ionide *F# Interactive* output pane, ANSI escape bytes are rendered literally as\x1b[31m...text — those panes are text widgets that don't dispatch through a VT terminal emulator. The fix: detect vianot Console.IsOutputRedirected && not (isNull (Environment.GetEnvironmentVariable "VSAPPIDDIR"))(Visual Studio setsVSAPPIDDIR; Ionide / VS Code setsVSCODE_PIDandTERM_PROGRAM=vscode) to skip ANSI in IDE-hosted FSI sessions, or setNO_COLOR=1in the FSI launch profile. Capability gating:System.Console.IsOutputRedirected(true when piped / redirected — skip colour to avoid poisoning files),System.Environment.GetEnvironmentVariable("NO_COLOR")(universal opt-out), andSystem.Console.OutputEncoding <- System.Text.Encoding.UTF8(box-drawing + emoji safety — defaults vary by OS / culture).