ANSI escape codes in C# — Console.Write, Spectre.Console, raw \x1b
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.
Recommended libraries
- Spectre.Console
The de facto modern .NET console toolkit — markup syntax (`[red]error[/]`, `[bold yellow on blue]warn[/]`), tables, trees, progress, prompts, exception rendering. Auto-detects terminal capability (TrueColor / 256 / 16 / no-colour) and falls back gracefully. Emits standard ANSI under the hood.
- Pastel
Lightweight string-extension API: `"error".Pastel(Color.Red).PastelBg(Color.Black)`. Returns a string with the ANSI bytes baked in — drop it anywhere `Console.WriteLine` expects a string. No state, no init, supports TrueColor.
- Crayon
Fluent colour-DSL: `Output.Red("error")`, `Output.Bright.Blue("info")`. Composable, allocation-light, capability-gated via `NO_COLOR`. Small surface area when you don't need Spectre's full TUI machinery.
- Sharprompt
Interactive-prompt library — `Prompt.Select("Pick one", choices)`, `Prompt.Input<string>("Name")`, password, multi-select, autocomplete. Built on the same ANSI / VT primitives this site documents; pairs well with Spectre.Console for richer UIs.
Idiomatic patterns
using System;
using System.Text;
class Program
{
static void Main()
{
// Box-drawing + emoji safety — defaults vary by OS / culture.
Console.OutputEncoding = Encoding.UTF8;
// \x1b is the ESC byte; C# string literals accept it verbatim.
Console.WriteLine("\x1b[1;31merror:\x1b[0m permission denied");
Console.WriteLine("\x1b[33mwarn:\x1b[0m deprecated flag");
Console.WriteLine("\x1b[32mok:\x1b[0m 142 tests passed");
}
}using Spectre.Console;
// dotnet add package Spectre.Console
AnsiConsole.MarkupLine("[bold red]error:[/] permission denied");
AnsiConsole.MarkupLine("[yellow]warn:[/] deprecated flag");
AnsiConsole.MarkupLine("[green]ok:[/] 142 tests passed");
// Truecolor — RGB triplet or hex:
AnsiConsole.MarkupLine("[#ff8000]orange truecolor[/]");
AnsiConsole.MarkupLine("[rgb(255,128,0)]same, RGB form[/]");
// Tables and progress are first-class:
var table = new Table().AddColumns("File", "Status");
table.AddRow("app.log", "[green]ok[/]");
table.AddRow("db.log", "[red]missing[/]");
AnsiConsole.Write(table);using System;
static bool AnsiCapable()
{
// The Unix-standard kill switch — honour first.
if (Environment.GetEnvironmentVariable("NO_COLOR") is not null) return false;
// Piped or redirected — don't poison logs / files with escape bytes.
if (Console.IsOutputRedirected) return false;
return true;
}
static string Style(string text, string sgr)
=> AnsiCapable() ? $"\x1b[{sgr}m{text}\x1b[0m" : text;
Console.WriteLine(Style("OK", "32"));
Console.WriteLine(Style("FAIL", "1;31"));using System;
using System.Runtime.InteropServices;
// Windows 10 1607+ / Windows Terminal / Conhost on 1709+ parse VT by
// default — this block is only needed if you target older builds. On
// non-Windows the P/Invoke just no-ops and falls through.
const int STD_OUTPUT_HANDLE = -11;
const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
static void EnableVtOnWindows()
{
if (!OperatingSystem.IsWindows()) return;
var handle = GetStdHandle(STD_OUTPUT_HANDLE);
if (!GetConsoleMode(handle, out var mode)) return;
SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
}
EnableVtOnWindows();
Console.WriteLine("\x1b[32mvt ready\x1b[0m");