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 anywhereConsole.WriteLineexpects a string. No state, no init, supports TrueColor. - Crayon
Fluent colour-DSL:
Output.Red("error"),Output.Bright.Blue("info"). Composable, allocation-light, capability-gated viaNO_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");
}
}[red]error[/]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");