The \e Escape Sequence: First-Class Terminal Escape Codes in C#
C# 13 adds \e as an escape sequence for the ESC character (Unicode U+001B, decimal 27). It is a small addition, but one that meaningfully improves the readability of terminal-oriented code. If you have ever written ANSI escape codes in C#, you know the pain this solves.
The Problem
ANSI escape sequences control terminal formatting — colours, cursor movement, text styles. They all begin with the ESC character followed by [ and a command. Before C# 13, expressing ESC required one of several ugly options:
// Option 1: Unicode escape — cryptic
string red = "\u001b[31m";
// Option 2: Hex escape — slightly less cryptic
string red = "\x1b[31m";
// Option 3: Cast from integer — verbose
string red = (char)27 + "[31m";
// Option 4: String interpolation with a constant — over-engineered
const char Esc = '\x1b';
string red = $"{Esc}[31m";
All of these work, but none of them communicate "this is an ANSI escape sequence" at a glance. The \x1b form has an additional hazard: \x consumes up to four hex digits, so \x1b[31m does not do what it looks like if the characters after \x1b happen to be valid hex digits in a different context.
The Solution
C# 13 adds \e as a dedicated escape for the ESC character:
string red = "\e[31m";
string reset = "\e[0m";
string bold = "\e[1m";
This is immediately recognisable to anyone familiar with terminal programming. The \e prefix mirrors conventions in bash, Python, and other languages.
Practical Terminal Formatting
Here is a simple helper for coloured console output:
public static class AnsiColour
{
public const string Red = "\e[31m";
public const string Green = "\e[32m";
public const string Yellow = "\e[33m";
public const string Blue = "\e[34m";
public const string Magenta = "\e[35m";
public const string Cyan = "\e[36m";
public const string Reset = "\e[0m";
public const string Bold = "\e[1m";
public const string Dim = "\e[2m";
public const string Underline = "\e[4m";
public static string Colourise(string text, string colour)
=> $"{colour}{text}{Reset}";
}
Console.WriteLine(AnsiColour.Colourise("Build succeeded", AnsiColour.Green));
Console.WriteLine(AnsiColour.Colourise("3 warnings", AnsiColour.Yellow));
Console.WriteLine(AnsiColour.Colourise("1 error", AnsiColour.Red));
Compare the readability of those constants with the equivalent \u001b[31m versions.
Beyond Colours
ANSI escape sequences control much more than text colour. Cursor movement, screen clearing, and terminal title setting all use the same ESC prefix:
public static class Terminal
{
// Cursor movement
public static string MoveUp(int lines) => $"\e[{lines}A";
public static string MoveDown(int lines) => $"\e[{lines}B";
public static string MoveTo(int row, int col) => $"\e[{row};{col}H";
// Screen control
public const string ClearScreen = "\e[2J";
public const string ClearLine = "\e[2K";
// Terminal title
public static string SetTitle(string title) => $"\e]0;{title}\a";
// Progress bar example
public static void ShowProgress(int percentage)
{
var filled = percentage / 2;
var bar = new string('#', filled) + new string('-', 50 - filled);
Console.Write($"\e[2K\r[{bar}] {percentage}%");
}
}
Building a CLI Tool
A more complete example — a simple status display for a build tool:
public class BuildReporter
{
private int _passed;
private int _failed;
public void ReportPass(string testName)
{
_passed++;
Console.WriteLine($" \e[32m✓\e[0m {testName}");
}
public void ReportFail(string testName, string reason)
{
_failed++;
Console.WriteLine($" \e[31m✗\e[0m {testName}");
Console.WriteLine($" \e[2m{reason}\e[0m");
}
public void PrintSummary()
{
Console.WriteLine();
Console.Write($"\e[1m{_passed + _failed} tests: ");
if (_failed == 0)
Console.WriteLine($"\e[32m{_passed} passed\e[0m");
else
Console.WriteLine($"\e[32m{_passed} passed\e[0m, \e[31m{_failed} failed\e[0m");
}
}
Every colour code in this example reads naturally because \e makes the intent obvious.
Compatibility Note
The \e escape produces the same byte value (0x1B) regardless of the target platform. However, ANSI escape sequence support varies by terminal. Modern terminals on Linux, macOS, and Windows Terminal all support ANSI codes. The legacy Windows console (conhost) requires enabling virtual terminal processing first:
// Enable ANSI support on legacy Windows console
[DllImport("kernel32.dll")]
static extern bool SetConsoleMode(IntPtr handle, uint mode);
[DllImport("kernel32.dll")]
static extern IntPtr GetStdHandle(int handle);
var stdout = GetStdHandle(-11);
SetConsoleMode(stdout, 0x0004); // ENABLE_VIRTUAL_TERMINAL_PROCESSING
In practice, if you are targeting .NET 9 or later, your users are almost certainly on a terminal that supports ANSI codes natively.
A Tiny Change That Matters
The \e escape sequence will not change how you architect your applications. But it makes terminal-oriented code noticeably more readable and less error-prone. If you write CLI tools, logging formatters, or diagnostic output, it is one of those small quality-of-life improvements that you will appreciate every time you use it.