C# Spectre.Console
last modified October 18, 2026
In this article we show how to create C# console applications using the Spectre.Console library.
Spectre.Console is a .NET library for creating rich console
applications. It supports colours and styles in terminal text. It provides
widgets such as rules, panels, grids, tables, trees, and bar charts, animated
progress bars and spinners, and interactive prompts.
$ dotnet add package Spectre.Console
We add the library to the project.
The figures in this article show the output in a terminal with a dark background. Spectre.Console detects the capabilities of the terminal; when the output is redirected to a file or a pipe, it writes plain text without colours.
Spectre.Console colours
We can write coloured text with the AnsiConsole.Markup and
AnsiConsole.MarkupLine methods.
using Spectre.Console;
AnsiConsole.MarkupLine("[steelblue]an old falcon[/]");
AnsiConsole.MarkupLine("[#ff0000]an old falcon[/]");
AnsiConsole.MarkupLine("[rgb(25,0,255)]an old falcon[/]");
AnsiConsole.MarkupLine("[bold gray on blue]an old falcon[/]");
AnsiConsole.MarkupLine("[bold steelblue on white]an old falcon[/]");
The example writes text in different colours. The markup is enclosed in square
brackets; [/] closes the last opened tag.
AnsiConsole.MarkupLine("[steelblue]an old falcon[/]");
AnsiConsole.MarkupLine("[#ff0000]an old falcon[/]");
AnsiConsole.MarkupLine("[rgb(25,0,255)]an old falcon[/]");
The colour of the text can be specified in different ways. We can use names, hexadecimal, and RGB codes.
AnsiConsole.MarkupLine("[bold gray on blue]an old falcon[/]");
AnsiConsole.MarkupLine("[bold steelblue on white]an old falcon[/]");
With the on keyword, we also set the colour of the background.
Spectre.Console styles
There are predefined styles, such as bold, italic, and strikethrough.
using Spectre.Console;
AnsiConsole.MarkupLine("[dim]an old falcon[/]");
AnsiConsole.MarkupLine("[italic]an old falcon[/]");
AnsiConsole.MarkupLine("[bold]an old falcon[/]");
AnsiConsole.MarkupLine("[underline]an old falcon[/]");
AnsiConsole.MarkupLine("[strikethrough]an old falcon[/]");
AnsiConsole.MarkupLine("[bold italic]an old falcon[/]");
AnsiConsole.MarkupLine("[dim underline]an old falcon[/]");
AnsiConsole.MarkupLine("[bold yellow]an [underline]old[/] falcon[/]");
string user = "[admin]";
AnsiConsole.MarkupLine($"User: [green]{Markup.Escape(user)}[/]");
AnsiConsole.MarkupLineInterpolated($"User: [green]{user}[/]");
In the program, we apply various styles to the text.
AnsiConsole.MarkupLine("[bold italic]an old falcon[/]");
Styles can be combined with each other and with colours.
AnsiConsole.MarkupLine("[bold yellow]an [underline]old[/] falcon[/]");
Tags can be nested; the inner text has both styles.
string user = "[admin]";
AnsiConsole.MarkupLine($"User: [green]{Markup.Escape(user)}[/]");
AnsiConsole.MarkupLineInterpolated($"User: [green]{user}[/]");
Square brackets in data would be interpreted as markup; here the program would
fail with an InvalidOperationException, because there is no colour
or style named admin. We must escape the data with Markup.Escape
or use MarkupLineInterpolated, which escapes the interpolated
values automatically.
Spectre.Console rules
A rule is a horizontal line with an optional title. The title can be aligned to the left, centre, or right. Rules are used to separate sections of the output.
using Spectre.Console;
var rule = new Rule("[steelblue]Python[/]");
rule.LeftJustified();
AnsiConsole.Write(rule);
AnsiConsole.WriteLine("""
Python is a general-purpose, dynamic, object-oriented programming language.
The design purpose of the Python language emphasizes programmer productivity
and code readability.
""");
var rule2 = new Rule("[skyblue1]F#[/]");
rule2.Centered();
AnsiConsole.Write(rule2);
AnsiConsole.WriteLine("""
F# is a universal programming language for writing succinct, robust and
performant code.
""");
var rule3 = new Rule("[indianred]Go[/]");
rule3.RightJustified();
rule3.Style = Style.Parse("indianred dim");
AnsiConsole.Write(rule3);
AnsiConsole.WriteLine("""
Go is an open source programming language that makes it easy to build simple,
reliable, and efficient software.
""");
In the program, we use three rules.
var rule = new Rule("[steelblue]Python[/]");
rule.LeftJustified();
AnsiConsole.Write(rule);
We create the first rule. The title is in steelblue colour and is aligned to
the left. Widgets are rendered with AnsiConsole.Write; a rule
spans the whole width of the console.
rule3.Style = Style.Parse("indianred dim");
The Style property sets the style of the line itself.
Spectre.Console panel
A panel is a widget which draws a box around its content.
using Spectre.Console;
var txt1 = """
Python is a general-purpose, dynamic, object-oriented
programming language. The design purpose of the Python language
emphasizes programmer productivity and code readability.
""";
var pnl1 = new Panel(txt1)
{
Header = new PanelHeader("Python")
};
AnsiConsole.Write(pnl1);
var txt2 = """
F# is a universal programming language for writing succinct,
robust and performant code.
""";
var pnl2 = new Panel(txt2)
.Header("[skyblue1]F#[/]", Justify.Center)
.RoundedBorder()
.BorderColor(Color.SkyBlue1)
.Padding(2, 1);
AnsiConsole.Write(pnl2);
The program creates two panels.
var pnl1 = new Panel(txt1)
{
Header = new PanelHeader("Python")
};
A panel consists of the content, an optional header, and a border.
var pnl2 = new Panel(txt2)
.Header("[skyblue1]F#[/]", Justify.Center)
.RoundedBorder()
.BorderColor(Color.SkyBlue1)
.Padding(2, 1);
Widgets are usually configured with fluent extension methods. We centre the header, use a rounded border in sky blue colour, and set the horizontal and vertical padding.
Spectre.Console JSON
Spectre.Console can pretty-print JSON data.
$ dotnet add package Spectre.Console.Json
We need to add the Spectre.Console.Json package.
using Spectre.Console;
using Spectre.Console.Json;
using var client = new HttpClient();
var content = await client.GetStringAsync("https://httpbin.org/json");
var json = new JsonText(content);
AnsiConsole.Write(
new Panel(json)
.Header("Slideshow")
.Collapse()
.RoundedBorder()
.BorderColor(Color.CadetBlue));
In the example, we retrieve JSON data from a web resource and display it in a panel.
var json = new JsonText(content);
The JsonText widget parses the JSON string and renders it with
indentation and syntax colouring.
AnsiConsole.Write(
new Panel(json)
.Header("Slideshow")
.Collapse()
.RoundedBorder()
.BorderColor(Color.CadetBlue));
Widgets can be nested. The JsonText is rendered in a panel. By
default, a panel expands to the width of the console; Collapse
shrinks it to the width of its content.
Spectre.Console grid
A grid is a widget which shows data in columns and rows, without borders.
using System.Globalization;
using Spectre.Console;
var uptime = TimeSpan.FromMilliseconds(Environment.TickCount64);
var grid = new Grid()
.AddColumn(new GridColumn().NoWrap().PadRight(4))
.AddColumn()
.AddRow("[bold]OS[/]", Environment.OSVersion.ToString())
.AddRow("[bold].NET[/]", Environment.Version.ToString())
.AddRow("[bold]Processors[/]", Environment.ProcessorCount.ToString())
.AddRow("[bold]64-bit process[/]", Environment.Is64BitProcess.ToString())
.AddRow("[bold]Culture[/]", CultureInfo.CurrentCulture.Name)
.AddRow("[bold]Uptime[/]", $"{(int) uptime.TotalHours} h {uptime.Minutes} min");
AnsiConsole.Write(grid);
In the program, we display information about the system in a grid.
.AddColumn(new GridColumn().NoWrap().PadRight(4)) .AddColumn()
We add two columns. The first one is not wrapped and has a right padding of four characters.
.AddRow("[bold]OS[/]", Environment.OSVersion.ToString())
The AddRow method takes strings with markup. Data that may contain
square brackets must be escaped with Markup.Escape.
Spectre.Console table
More complex tabular data can be displayed in a Table widget.
using Spectre.Console;
List<Country> countries =
[
new("China", "Beijing", 1_409_670_000, 9_596_961),
new("India", "New Delhi", 1_428_627_663, 3_287_263),
new("United States", "Washington, D.C.", 334_914_895, 9_833_517),
new("Indonesia", "Jakarta", 277_534_122, 1_904_569),
new("Slovakia", "Bratislava", 5_424_687, 49_035)
];
var table = new Table()
.Border(TableBorder.Rounded)
.BorderColor(Color.SteelBlue)
.Title("[bold]Countries[/]")
.AddColumn("Name")
.AddColumn("Capital")
.AddColumn(new TableColumn("Population").RightAligned())
.AddColumn(new TableColumn("Area (km²)").RightAligned());
foreach (var c in countries.OrderByDescending(c => c.Population))
{
table.AddRow(c.Name, c.Capital, $"{c.Population:N0}", $"{c.Area:N0}");
}
table.Columns[0].Footer("[grey]Total[/]");
table.Columns[2].Footer($"[grey]{countries.Sum(c => c.Population):N0}[/]");
AnsiConsole.Write(table);
record Country(string Name, string Capital, long Population, int Area);
We display a list of countries in a table.
var table = new Table()
.Border(TableBorder.Rounded)
.BorderColor(Color.SteelBlue)
.Title("[bold]Countries[/]")
We set the border style and colour and the title of the table.
Spectre.Console has many border styles, including Ascii,
Square, Double, Markdown, and
None.
.AddColumn(new TableColumn("Population").RightAligned())
Numbers are easier to compare when they are aligned to the right.
table.Columns[0].Footer("[grey]Total[/]");
table.Columns[2].Footer($"[grey]{countries.Sum(c => c.Population):N0}[/]");
Columns can have footers; we display the total population.
Spectre.Console tree
The Tree widget displays hierarchical data.
using Spectre.Console;
var root = new Tree("[yellow]Programming languages[/]");
var compiled = root.AddNode("[steelblue]Compiled[/]");
compiled.AddNodes("C", "C++", "Rust", "Go");
var managed = root.AddNode("[steelblue]Managed runtime[/]");
var dotnet = managed.AddNode(".NET");
dotnet.AddNodes("C#", "F#", "Visual Basic");
managed.AddNode("JVM").AddNodes("Java", "Kotlin", "Scala");
var scripting = root.AddNode("[steelblue]Scripting[/]");
scripting.AddNodes("Python", "Ruby", "Perl");
AnsiConsole.Write(root);
We create a tree of programming languages.
var compiled = root.AddNode("[steelblue]Compiled[/]");
compiled.AddNodes("C", "C++", "Rust", "Go");
The AddNode method adds a child node and returns it, so that we
can add further nodes to it. AddNodes adds several leaf nodes at
once.
Spectre.Console bar chart
Simple bar charts can be drawn with the BarChart widget.
using Spectre.Console;
var chart = new BarChart()
.Width(60)
.Label("[bold underline]Fruit sales (tons)[/]")
.CenterLabel()
.AddItem("Apples", 34, Color.Green)
.AddItem("Oranges", 27, Color.Orange1)
.AddItem("Bananas", 41, Color.Yellow)
.AddItem("Plums", 12, Color.Purple)
.AddItem("Cherries", 22, Color.Red);
AnsiConsole.Write(chart);
The chart is 60 characters wide. Each bar has a label, a value, and a colour; the bars are scaled to the largest value.
Spectre.Console status
The Status display shows an animated spinner with a message while
a long-running operation is in progress.
using Spectre.Console;
string[] files = ["data.csv", "users.json", "report.pdf"];
await AnsiConsole.Status()
.Spinner(Spinner.Known.Dots)
.StartAsync("Processing...", async ctx =>
{
foreach (var file in files)
{
ctx.Status($"Processing [blue]{file}[/]");
await Task.Delay(1000);
AnsiConsole.MarkupLine($"[green]✓[/] {file}");
}
});
AnsiConsole.MarkupLine("[bold]Done[/]");
We simulate processing of three files.
ctx.Status($"Processing [blue]{file}[/]");
The context object lets us change the message while the spinner is running.
Lines written with AnsiConsole during the operation appear above
the spinner.
$ dotnet run ✓ data.csv ✓ users.json ✓ report.pdf Done
This is the final output; the spinner line disappears when the operation finishes.
Spectre.Console progress
The Progress display shows one or more progress bars.
using Spectre.Console;
await AnsiConsole.Progress()
.Columns(
new TaskDescriptionColumn(),
new ProgressBarColumn(),
new PercentageColumn(),
new SpinnerColumn())
.StartAsync(async ctx =>
{
var download = ctx.AddTask("Downloading");
var extract = ctx.AddTask("Extracting", maxValue: 50);
while (!ctx.IsFinished)
{
await Task.Delay(50);
download.Increment(2);
if (download.Value > 40)
{
extract.Increment(1.5);
}
}
});
We simulate two tasks: a download and an extraction that starts while the download is still running.
.Columns(
new TaskDescriptionColumn(),
new ProgressBarColumn(),
new PercentageColumn(),
new SpinnerColumn())
We choose the columns of the display. There are also columns for the elapsed and remaining time and for the transfer speed.
var extract = ctx.AddTask("Extracting", maxValue: 50);
Each task has a maximum value, 100 by default. The display is finished when all tasks reach their maximum values.
$ dotnet run Downloading ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% Extracting ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100%
This is the final state of the display; the spinners disappear when the tasks are finished.
Spectre.Console prompts
Spectre.Console provides prompts for reading and validating user input.
using Spectre.Console;
var name = AnsiConsole.Ask<string>("What is your [green]name[/]?");
var age = AnsiConsole.Prompt(
new TextPrompt<int>("How old are you?")
.Validate(n => n is > 0 and < 130
? ValidationResult.Success()
: ValidationResult.Error("[red]Invalid age[/]")));
var lang = AnsiConsole.Prompt(
new SelectionPrompt<string>()
.Title("Which is your favourite [green]language[/]?")
.AddChoices("C#", "F#", "Python", "Go", "Rust"));
var confirm = AnsiConsole.Confirm("Save the profile?");
AnsiConsole.MarkupLineInterpolated($"[bold]{name}[/], {age}, likes {lang}; saved: {confirm}");
We ask the user for a name, age, favourite language, and a confirmation.
var name = AnsiConsole.Ask<string>("What is your [green]name[/]?");
The Ask method shows a prompt and converts the answer to the
given type.
var age = AnsiConsole.Prompt(
new TextPrompt<int>("How old are you?")
.Validate(n => n is > 0 and < 130
? ValidationResult.Success()
: ValidationResult.Error("[red]Invalid age[/]")));
A TextPrompt can validate the input. If the answer is not a number
or the validation fails, the error message is shown and the question is asked
again.
var lang = AnsiConsole.Prompt(
new SelectionPrompt<string>()
.Title("Which is your favourite [green]language[/]?")
.AddChoices("C#", "F#", "Python", "Go", "Rust"));
A SelectionPrompt shows a list of choices; the user moves in the
list with the arrow keys and selects an item with Enter. The list is removed
from the screen after the selection. There is also a
MultiSelectionPrompt for selecting several items.
var confirm = AnsiConsole.Confirm("Save the profile?");
The Confirm method asks a yes/no question.
$ dotnet run What is your name? Jan How old are you? 200 Invalid age How old are you? 40 Save the profile? [y/n] (y): y Jan, 40, likes Python; saved: True
Prompts require an interactive terminal. When the input is redirected, they
throw an InvalidOperationException; programs that are also used in scripts should accept the
values as command line arguments too.
Source
In this article we have used Spectre.Console to create console
applications in C#.
Author
List all C# tutorials.