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
- Python
Python's stdlib has no built-in colour helper, but every byte you would write by hand is fine — strings can carry `\x1b` escapes directly. For cross-platform support (Windows `cmd.exe` before 1909 didn't parse ANSI by default), reach for `colorama`. For richer TUIs, `rich` and `prompt_toolkit` build on the same byte forms documented here.
- Go
Go's `fmt.Print*` family is byte-clean — `\x1b[31m` just works on Unix and on Windows 10+ (when `ENABLE_VIRTUAL_TERMINAL_PROCESSING` is set, which `golang.org/x/sys/windows` can flip on). For anything more than a handful of escapes, reach for `fatih/color` or, for full TUIs, the Charmbracelet stack (`lipgloss` + `bubbletea`).
- JavaScript / Node.js
Node's `process.stdout.write` and `console.log` are 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, `chalk` is the canonical choice; `picocolors` is 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 uses `Deno.stdout.writeSync` (or `console.log`), Bun accepts both Node's `process.stdout` shim and its own `Bun.write(Bun.stdout, …)`. All three are byte-clean. For typed ergonomics reach for `picocolors` (zero-dep, works in every runtime via `npm:` / `jsr:` / direct import), Deno's bundled `@std/fmt/colors`, or `kolorist` for a tiny isomorphic option. `chalk` 5+ is ESM-only and works under Node / Bun / `npm:chalk` in Deno.
- Node.js (CLI tools)
Node-CLI surfaces where `/use/javascript` only sketches: `process.stdout.isTTY` for capability detection (false when piped, redirected, or running under CI), `process.stdout.columns` for adaptive width, the ESM-only chalk 5+ break that bites every CommonJS project, and the `node:tty` + `readline` ANSI 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.
- 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 piped `Get-ChildItem` strips colour automatically. For PS 5.1 (Windows-default) you fall back to `Write-Host -ForegroundColor` or raw VT via the `` `e `` escape token (PS 7+) / `[char]27` (PS 5.1). Windows 10+ Conhost / Windows Terminal both parse ANSI natively — no `colorama`-style shim needed.
- Bash / shell
Bash has two reliable ways to emit escape bytes: `printf` with `\033` / `\x1b` (POSIX-portable), and `tput` (terminfo-driven, the safest because it adapts to `$TERM`). `echo -e` works 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.
- C
C has no string-escape for ESC — write `\x1b` (hex) or `\033` (octal). `printf` and `fputs` are byte-clean on every platform; on Windows 10+ enable virtual terminal processing via `SetConsoleMode(..., ENABLE_VIRTUAL_TERMINAL_PROCESSING)` first. For anything resembling a TUI (menus, windows, mouse) link `ncurses` rather than hand-rolling.
- C#
C# string literals support `\x1b` directly — `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.Console` is the de facto modern .NET TUI / styled-console library (markup syntax `[red]error[/]`, tables, progress, prompts) and `Pastel` gives you a one-liner extension method (`"hello".Pastel(Color.Red)`). On legacy Windows builds before 1607 you must flip `ENABLE_VIRTUAL_TERMINAL_PROCESSING` via a `SetConsoleMode` P/Invoke before the first write. Set `Console.OutputEncoding = Encoding.UTF8` to keep box-drawing and emoji intact.
- F#
F# string literals accept `\x1b` directly (since F# 4.5, August 2018), `\u001b` (Unicode codepoint — works in every F# version including .NET Framework F# 2.0), and `\NNN` triplets — but **F# `\NNN` is DECIMAL, not octal** (the .NET runtime quirk shared with C# / VB.NET / PowerShell, and the opposite of C / Erlang / Java where `\033` is 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 is `printfn "\x1b[1;31merror:\x1b[0m %s" msg` — `printf` / `printfn` is F#'s type-checked printf family (the format string is parsed at COMPILE time, unlike C# / Java where format strings are validated at runtime). `%s` interpolates a string, `%d` an int, `%A` any value via structured pretty-print. Escape bytes embedded in the format string pass through to `System.Console.Out` unchanged. The `printfn` newline is platform-aware (`\r\n` on Windows, `\n` on Unix) — use plain `printf` if you're building a single line incrementally. For anything richer than ad-hoc escapes, **`Spectre.Console`** is the de facto modern .NET console toolkit — works identically from F# (`AnsiConsole.MarkupLine "[bold red]error:[/] permission denied"`). Pair it with **`Argu`** (F#'s canonical CLI parser — auto-coloured `--help` output, type-safe argument records via discriminated unions) for full F# script ergonomics. For F#-idiomatic templated coloured output reach for **`BlackFox.ColoredPrintf`** — `cprintfn "$red[error:] %s" msg` uses `$colour[...]` template syntax that compiles to standard ANSI. For F# build scripts (FAKE) the **`Fake.Core.Trace`** module 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 fsi` from a normal terminal prompt parses ANSI in `printfn` output — 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 via `not Console.IsOutputRedirected && not (isNull (Environment.GetEnvironmentVariable "VSAPPIDDIR"))` (Visual Studio sets `VSAPPIDDIR`; Ionide / VS Code sets `VSCODE_PID` and `TERM_PROGRAM=vscode`) to skip ANSI in IDE-hosted FSI sessions, or set `NO_COLOR=1` in 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), and `System.Console.OutputEncoding <- System.Text.Encoding.UTF8` (box-drawing + emoji safety — defaults vary by OS / culture).
- 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 for `Rainbow` (the most-starred Swift ANSI string library), which adds `String` extensions like `"error".red.bold`. Apple's own `swift-argument-parser` ships coloured `--help` output via the same primitives. For full TUI work (panels, mouse, raw mode) `TermKit` is the ncurses-style option. Capability gating uses `isatty(STDOUT_FILENO)` from `Darwin` / `Glibc`, or `FileHandle.standardOutput.isTerminal` on Swift 5.7+ where it's bridged.
- Rust
Rust string literals support `\x1b` directly — `println!("\x1b[31mred\x1b[0m")` works with no dependencies. For ergonomic colour APIs reach for `owo-colors` (zero-allocation, compile-time) or `colored` (more dynamic). For NO_COLOR / isatty handling without writing the env-check yourself, `termcolor` mirrors the Go pattern. For full TUI work — windows, cursor, raw mode, mouse — use `crossterm`; for higher-level layouts, `ratatui` (formerly tui-rs) sits on top of it.
- Ruby
Ruby's double-quoted strings accept `\e` as the ESC literal (no need for `\x1b` or `\033`), and `puts` / `print` / `STDOUT.write` are byte-clean on every platform. Ruby 3.0+ on Windows auto-enables `ENABLE_VIRTUAL_TERMINAL_PROCESSING` on the standard streams, so `\e[31m` lights up Conhost and Windows Terminal without a `colorama`-style shim. For ergonomic styling: `colorize` monkey-patches `String` with chainable colour methods (`"oops".red.bold`), `term-ansicolor` (Florian Frank's classic — powers `pry` and `rspec`) offers both module-call (`Term::ANSIColor.red(...)`) and mixin styles, and `pastel` (from the TTY toolkit) ships isolated SGR helpers that auto-detect `NO_COLOR` and `CLICOLOR_FORCE` without polluting `String`. For full TUIs reach for the rest of the TTY toolkit — `tty-prompt`, `tty-spinner`, `tty-cursor`, `tty-progressbar`.
- 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 `\e` in double-quoted strings; older code uses `"\x1b"` or `chr(27)`). On Windows, Conhost since Windows 10 1709 parses VT natively, but PHP recommends explicitly flipping the mode with `sapi_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, Laravel `artisan`, Symfony `console`, and Drupal `drush`. Its `OutputFormatter` uses 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 like `text-red-500`, `bg-blue-500`, `font-bold`) and underpins PHPStan, Pest, Laravel Zero. **laravel/prompts** offers modern interactive prompts that work standalone outside Laravel.
- Java
Java string literals have no `\e` escape — 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), call `AnsiConsole.systemInstall()` from **Jansi** — it swaps `System.out` and `System.err` with 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's `readline` (raw mode, line editing, history, completion, masked password, terminal capability detection) — the foundation under Spring Shell, JBang, Groovy's `groovysh`, and OpenJDK's `jshell`. **picocli** annotation-drives CLI arg parsing with ANSI-aware help formatters via `Help.Ansi.AUTO` (honours `NO_COLOR` and `isatty`). For full-screen TUIs reach for **Lanterna** — a Swing-style hierarchy of `Window` / `Panel` / `Component` rendered to ANSI.
- Kotlin
Kotlin string literals use `\u001B` for the ESC byte — Kotlin has no `\x` hex 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 for `mordant`, the most popular Kotlin ANSI library — it auto-detects terminal capability, respects `NO_COLOR`, and downgrades cleanly when piped. For full interactive TUIs `kotter` provides a coroutine-friendly DSL. `picocli` (a JVM CLI argument parser) ships built-in ANSI colour support for `--help` and 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 set `org.gradle.console=plain` in `gradle.properties`) to see escape bytes verbatim. Android Logcat does not interpret ANSI; use `mordant`'s `Theme.PlainAscii` or capability-detect when targeting Android.
- Dart
Dart's `dart:io` `stdout` already exposes the canonical capability check most other ecosystems force you to roll yourself — `stdout.supportsAnsiEscapes` returns `true` on a TTY that understands VT sequences, `false` when piped, redirected, or running on a legacy Conhost without VT enabled. Pair it with raw `stdout.write('\x1B[31m…\x1B[0m')` (Dart accepts both `\x1B` and `\u001B`) and you have a portable starting point with zero dependencies on macOS, Linux, and Windows Terminal / Conhost 1709+. For ergonomic colour APIs reach for `chalkdart` (the most popular Dart ANSI library — chained-extension API mirrored after Node's `chalk`), `tint` (compact, modern), or `cli_util` (Google's first-party CLI helpers including an ANSI module). The Flutter SDK's own `flutter` CLI uses these primitives — its progress / build-status output is a canonical reference for Dart-shaped capability gating.
- Perl
Perl supports the `\e` escape 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 for **`Term::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, and `colorstrip("\e[31mred\e[0m")` removes ANSI cleanly (a popular long-tail SERP target). Capability gating uses Perl's idiomatic `-t STDOUT` file test (returns true on a TTY), `IO::Interactive::Tiny::is_interactive` for a portable wrapper, and `$ENV{NO_COLOR}` for the cross-language kill switch. For full TUIs link **`Curses::UI`** (Perl binding for ncurses); for cap-database lookups, **`Term::Cap`** queries terminfo. Perl is still the canonical glue for sysadmin one-liners and log-processing pipelines — the `colorstrip` recipe 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 for **`crayon`** — the de-facto R ANSI library used by tidyverse, testthat, pkgdown, devtools: `crayon::red("error")` returns a styled string, `crayon::bold` and friends compose freely (`bold(red(...))`), and `make_style("#ff8000")` factory-builds a truecolor styler. The modern hadley/r-lib companion **`cli`** adds semantic helpers (`cli_alert_danger()`, `cli_h1()`, pluralized inline markup) on top of crayon's primitives. **`glue`** templates the same vocabulary in `glue_col("{red error}")`. For ANSI-aware string operations (the canonical R log scrubber lives here), **`fansi::strip_sgr`** removes every CSI sequence cleanly, and `fansi::nchar_ctl`/`substr_ctl` count display width and slice without corrupting bytes. Capability gating in R: `crayon::has_color()` is the authoritative call — it already respects `NO_COLOR`, `isatty(stdout())`, the `crayon.enabled` option, 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.
- 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 `\ESC` is the Haskell-idiomatic form (distinct from every other language this site documents — Haskell is the only mainstream language where the literal `\ESC` keyword 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) the **`ansi-terminal`** library hides the `SetConsoleMode` call behind `hSupportsANSI`. For the canonical helper reach for **`ansi-terminal`** — declarative `setSGR [SetColor Foreground Vivid Red, SetConsoleIntensity BoldIntensity]` API with explicit `Reset` clarity, cross-platform Windows VT enabling, named cursor / erase / scroll helpers, and a Type-Safe Way that catches bad combinations at compile time. **`rainbow`** layers a fluent `Text` / `String` colouring DSL on top — `chunk "error" & fore red & bold` builds a `Chunk` and `putChunkLn` renders it. For full TUIs link **`brick`** (vty-based, declarative panel-and-event-loop architecture — used by `lorri`, `matterhorn`, the `ghcid` TUI, `tasty-bench`-tui). For REPL-style line editing (history, completion, multi-line) reach for **`haskeline`** — the readline-equivalent that GHCi itself uses. Capability gating: `ansi-terminal`'s `hSupportsANSI stdout` is the canonical capability check (handles macOS/Linux/BSD isatty + Windows VT enablement); pair it with `lookupEnv "NO_COLOR"` (`System.Environment`) and `hIsTerminalDevice stdout` (`System.IO`) when you want to skip the library dep. The GHCi REPL parses ANSI in modern terminal hosts; `cabal repl` and `stack ghci` sometimes wrap stdout — known friction.
- Julia
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.
- Scala
Scala's string literals do **not** support `\e` or `\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 for **`fansi`** (`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 a `fansi.Str` value with structured colour spans rather than embedded bytes; `++` concatenates spans, `.overlay(fansi.Bold.On, 0, 5)` overlays attributes on a sub-range, `.render` materialises the byte string. **`scala.io.AnsiColor`** is 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 default `println` replacement. 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() != null` is the JVM-portable check (returns `null` when stdin/stdout is redirected). Pair with `sys.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=always` on the SBT command line, or set `ThisBuild / outputStrategy := Some(StdoutOutput)` in `build.sbt`, when launching scripts that need true colour passthrough. `scala-cli` and `mill` don't share this quirk.
- Clojure
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.
- 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. `puts` and `print` write 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 stdlib **`Colorize`** module (`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-checks `STDOUT.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 the **`term-cursor`** shard (`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 the **`term-screen`** shard (`Term::Screen.size # => {height, width}`). For a full blessed-equivalent TUI runtime (windows, focus, mouse, redraw loop) reach for **`crysterm`**, the most mature Crystal TUI library. Capability gating: `STDOUT.tty?` is the stdlib check — returns `false` when stdout is redirected to a file, pipe, or non-terminal IO. Pair with `ENV["NO_COLOR"]?` for the universal env-var convention; `Colorize.enabled = false` to 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 call `LibC.SetConsoleMode` with `ENABLE_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/terminal` is 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 via `Color(...)`, truecolor (with an explicit `enableTrueColors()` 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 `\002` followed 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 beyond `std/terminal` reach for: **`std/colors`** (stdlib colour-name table — `colWhite`, `colAliceBlue`, full HTML/CSS palette as `Color` constants, paired with `enableTrueColors()` to emit `38;2;R;G;B` from 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 with `std/terminal` for non-fullscreen output). Capability gating: `isatty(stdout)` from `std/terminal` returns `false` when stdout is redirected. Pair with `getEnv("NO_COLOR")` for the universal opt-out and `getEnv("FORCE_COLOR")` for opt-in. Windows VT mode: `std/terminal` calls `SetConsoleMode` with `ENABLE_VIRTUAL_TERMINAL_PROCESSING` automatically on first use of any colour helper, so you don't need a `when 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.
- 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 `\e` named escape. `print_string "\027[1;31merror:\027[0m permission denied\n"` writes raw bytes through the runtime's `stdout` channel, parsed natively on macOS, Linux, BSDs, and Windows 10+ Conhost 1709+. Reach for **`ANSITerminal`** (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 for **`fmt`** (the Dune-default formatter) — `Fmt.pr "@{<red>error@}: %s@." msg` parses semantic markup tags into SGR sequences and respects `Fmt.set_style_renderer Format.std_formatter `Ansi_tty`. Pair `fmt` with **`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 an `Notty.image` you compose with `<|>` / `<->` and render via `Notty_unix.Term`). Capability gating: `Unix.isatty Unix.stdout` (the `unix` library, ships with most OCaml installs) returns `false` when stdout is redirected. Pair with `Sys.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 install `utop-full` and use `UTop.set_phrase_terminator` / `UTop.set_show_box` to 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 `\NNN` IS octal, same as C), AND `"\^["` (caret-notation control character — `^A` through `^Z` map 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 — `~s` interpolates the second-argument list, `~n` is the platform newline, and ANSI bytes pass through `io:format/2` unchanged to the calling process's `group_leader` (which for shell-attached code is the user's terminal). For SGR helpers reach for **`cf`** (project-fifo/cf — "Erlang Colour Format") — extends `io: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 stdlib **`logger`** module (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 for **`lager`** (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 a `group_leader` that handles its I/O. In an interactive shell or escript, `group_leader` IS the user's terminal — ANSI passes through. When you build an OTP release (`rebar3 release` → `bin/myapp daemon` or `foreground`), the `group_leader` is reassigned: in `daemon` mode it's a logger process that writes to `log/erlang.log.N`, and ANSI bytes appear **literally** in the log file as `\e[31m...` text. The fix: use the `logger` module's coloured handler (which strips ANSI when its output destination is not a TTY — auto-detection via `io:rows()` returning `{error, enotsup}`), or detect via `application:get_env(kernel, standard_io_handler)` and gate your `io:format` colour calls explicitly. Capability gating: `io:rows()` returns `{ok, Rows}` when stdout is a TTY, `{error, enotsup}` when redirected. Pair with `os:getenv("NO_COLOR")` for the universal opt-out. Modern Erlang shell (`erl`, OTP 25+) parses ANSI in `io:format` output natively on all platforms including Windows (the BEAM emulator handles `SetConsoleMode` on first ANSI byte).
- Lua
Lua has no `\e` escape literal, but `\27` (decimal) works in every interpreter back to Lua 5.1, and `\x1b` (hex) works since Lua 5.2. `io.write` and `print` are 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 like `colors('%{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**.
- Zig
Zig's standard library has no colour helper, but byte literals are first-class — `"\x1b[31m"` is a `*const [5:0]u8` and writes through `std.io.getStdOut().writer().print` (or `std.debug.print` for quick prototyping) without any encoding work. Since Zig 0.11 the canonical capability gate is `std.io.tty.detectConfig(std.io.getStdOut())` — it returns a tagged union (`.no_color`, `.escape_codes`, `.windows_api`) that honours `NO_COLOR`, checks `isatty(2)`, and selects the right backend on Windows pre-VT Conhost. Wrap your write sites in `switch (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.
- Elixir
Elixir ships an `IO.ANSI` module 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 is `IO.ANSI.format/2`, which takes a list mixing atoms (the named escapes) and binaries (your text) and returns an iolist suitable for `IO.puts/1` or `IO.write/1` — e.g. `IO.puts(IO.ANSI.format([:bright, :red, "error: ", :reset, "permission denied"]))`. `IO.ANSI.enabled?/0` is the capability gate baked into stdlib: it honours the `:elixir` app env `:ansi_enabled` (set by `iex` to true on a tty, false on a pipe) plus the `NO_COLOR` env var. For more than ad-hoc prints: **Bunt** (savonarola/bunt) sugars `IO.ANSI` with named colour tags inside the string itself — `Bunt.puts([:red, "error: ", :default_color, "permission denied"])` or the inline form `Bunt.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, `mix` extensions, and `livebook` startup output. For full-screen TUIs reach for **Ratatouille** (ndreynolds/ratatouille) — an Elm-style `model / update / view` architecture rendered to ANSI via termbox2.