Skip to content
ConsoleMonster

Three ways to write it in C#

Fluent methods, object initializers or the Ui factories: three complete programs that print the same frame, and when each reads best.

Every widget can be written in three styles. They build the same objects, so they print the same output: the three programs on this page ran for it, and each printed the frame below cell for cell. Use whichever reads best for the case, and mix them freely in one program.

To run one, make a console project and replace its Program.cs:

dotnet new console -n Servers
cd Servers
dotnet add package ConsoleMonster
# Replace Program.cs with one of the programs below.
dotnet run

Fluent

A With... method for every settable property and Add... methods for every collection, plus typed tables: Table.For(items) reads each column's header from its expression. The whole layout is one expression that reads top to bottom. The docs and the gallery use this style.

dotnet run 88 columns24-bitFull
C#Program.cs
Server[] servers = {
    new("web01", "IIS", 8L * 1024 * 1024 * 1024, StatusKind.Pass),
    new("app01", "App", 16L * 1024 * 1024 * 1024, StatusKind.Warn),
    new("db01", "SQL", 64L * 1024 * 1024 * 1024, StatusKind.Fail)
};
double[] load = { 12, 18, 31, 64, 47, 38, 55, 70, 72, 41 };

// Fluent: each widget is created and configured in one expression, so the layout reads top to bottom.
Terminal.Write(new Rows()
    .WithGap(1)
    .Add(new Columns()
        .WithGap(1)
        .Add(new Panel(Table.For(servers)
            .Column(s => s.Name, c => c.WithIsKey())
            .Column(s => s.Role)
            .Column(s => s.Ram, c => c.WithPresenter(CellPresenters.Bytes))
            .Column(s => s.Status)
            .WithTotals(), "Servers"))
        .Add(new Panel(new Rows()
            .Add(new KeyValueList().AddValue("Servers", 3).AddValue("Failed", 1))
            .Add(new Sparkline().WithLabel("CPU").AddRange(load)), "Summary")))
    .Add(new TextBlock("[muted]Updated 10:02[/]")));

record Server(string Name, string Role, long Ram, StatusKind Status);
Full program
using ConsoleMonster;
using ConsoleMonster.Layout;
using ConsoleMonster.Presenters;
using ConsoleMonster.Styling;
using ConsoleMonster.Widgets;

Server[] servers = {
    new("web01", "IIS", 8L * 1024 * 1024 * 1024, StatusKind.Pass),
    new("app01", "App", 16L * 1024 * 1024 * 1024, StatusKind.Warn),
    new("db01", "SQL", 64L * 1024 * 1024 * 1024, StatusKind.Fail)
};
double[] load = { 12, 18, 31, 64, 47, 38, 55, 70, 72, 41 };

// Fluent: each widget is created and configured in one expression, so the layout reads top to bottom.
Terminal.Write(new Rows()
    .WithGap(1)
    .Add(new Columns()
        .WithGap(1)
        .Add(new Panel(Table.For(servers)
            .Column(s => s.Name, c => c.WithIsKey())
            .Column(s => s.Role)
            .Column(s => s.Ram, c => c.WithPresenter(CellPresenters.Bytes))
            .Column(s => s.Status)
            .WithTotals(), "Servers"))
        .Add(new Panel(new Rows()
            .Add(new KeyValueList().AddValue("Servers", 3).AddValue("Failed", 1))
            .Add(new Sparkline().WithLabel("CPU").AddRange(load)), "Summary")))
    .Add(new TextBlock("[muted]Updated 10:02[/]")));

record Server(string Name, string Role, long Ram, StatusKind Status);

Object initializers

The canonical model: each widget is a variable, set with properties (new Table { ShowTotals = true }) and filled with Add calls. It reads best when you set many options at once, fill rows in a loop, or keep a widget to change later.

C#Program.cs
Server[] servers = {
    new("web01", "IIS", 8L * 1024 * 1024 * 1024, StatusKind.Pass),
    new("app01", "App", 16L * 1024 * 1024 * 1024, StatusKind.Warn),
    new("db01", "SQL", 64L * 1024 * 1024 * 1024, StatusKind.Fail)
};
double[] load = { 12, 18, 31, 64, 47, 38, 55, 70, 72, 41 };

// Objects: each widget is a variable, set with properties and Add calls, then placed in the layout.
Table table = new Table { ShowTotals = true, AutoTotals = true };
table.AddColumn(new TableColumn("Name") { IsKey = true });
table.AddColumn(new TableColumn("Role"));
table.AddColumn(new TableColumn("Ram") { Presenter = CellPresenters.Bytes });
table.AddColumn(new TableColumn("Status"));
foreach (Server server in servers) {
    table.AddValues(server.Name, server.Role, server.Ram, server.Status);
}

KeyValueList summary = new KeyValueList();
summary.AddValue("Servers", 3);
summary.AddValue("Failed", 1);
Sparkline cpu = new Sparkline { Label = "CPU" };
cpu.AddRange(load);

Terminal.Write(new Rows(
    new Columns(
        new Panel(table, "Servers"),
        new Panel(new Rows(summary, cpu), "Summary")) { Gap = 1 },
    new TextBlock("[muted]Updated 10:02[/]")) { Gap = 1 });

record Server(string Name, string Role, long Ram, StatusKind Status);
Full program
using ConsoleMonster;
using ConsoleMonster.Layout;
using ConsoleMonster.Presenters;
using ConsoleMonster.Styling;
using ConsoleMonster.Widgets;

Server[] servers = {
    new("web01", "IIS", 8L * 1024 * 1024 * 1024, StatusKind.Pass),
    new("app01", "App", 16L * 1024 * 1024 * 1024, StatusKind.Warn),
    new("db01", "SQL", 64L * 1024 * 1024 * 1024, StatusKind.Fail)
};
double[] load = { 12, 18, 31, 64, 47, 38, 55, 70, 72, 41 };

// Objects: each widget is a variable, set with properties and Add calls, then placed in the layout.
Table table = new Table { ShowTotals = true, AutoTotals = true };
table.AddColumn(new TableColumn("Name") { IsKey = true });
table.AddColumn(new TableColumn("Role"));
table.AddColumn(new TableColumn("Ram") { Presenter = CellPresenters.Bytes });
table.AddColumn(new TableColumn("Status"));
foreach (Server server in servers) {
    table.AddValues(server.Name, server.Role, server.Ram, server.Status);
}

KeyValueList summary = new KeyValueList();
summary.AddValue("Servers", 3);
summary.AddValue("Failed", 1);
Sparkline cpu = new Sparkline { Label = "CPU" };
cpu.AddRange(load);

Terminal.Write(new Rows(
    new Columns(
        new Panel(table, "Servers"),
        new Panel(new Rows(summary, cpu), "Summary")) { Gap = 1 },
    new TextBlock("[muted]Updated 10:02[/]")) { Gap = 1 });

record Server(string Name, string Role, long Ram, StatusKind Status);
Prints the frame above.

Ui factories

using static ConsoleMonster.Ui; brings short factories (Rows, Columns, Panel, Table, KeyValue, Text, Sparkline and others), so the code has the shape of the screen. The With... methods are extension methods in each widget's own namespace, so ConsoleMonster.Layout and ConsoleMonster.Widgets stay among the using lines. With the factories, write Table(items) rather than Table.For(items), because the factory takes the name Table.

C#Program.cs
Server[] servers = {
    new("web01", "IIS", 8L * 1024 * 1024 * 1024, StatusKind.Pass),
    new("app01", "App", 16L * 1024 * 1024 * 1024, StatusKind.Warn),
    new("db01", "SQL", 64L * 1024 * 1024 * 1024, StatusKind.Fail)
};
double[] load = { 12, 18, 31, 64, 47, 38, 55, 70, 72, 41 };

// Ui factories: short functions build each widget, so the code has the shape of the screen.
Terminal.Write(Rows(
    Columns(
        Panel("Servers", Table(servers)
            .Column(s => s.Name, c => c.WithIsKey())
            .Column(s => s.Role)
            .Column(s => s.Ram, c => c.WithPresenter(CellPresenters.Bytes))
            .Column(s => s.Status)
            .WithTotals()),
        Panel("Summary", Rows(
            KeyValue(("Servers", 3), ("Failed", 1)),
            Sparkline(load).WithLabel("CPU")))
    ).WithGap(1),
    Text("[muted]Updated 10:02[/]")
).WithGap(1));

record Server(string Name, string Role, long Ram, StatusKind Status);
Full program
using ConsoleMonster;
using ConsoleMonster.Layout;
using ConsoleMonster.Presenters;
using ConsoleMonster.Styling;
using ConsoleMonster.Widgets;
using static ConsoleMonster.Ui;

Server[] servers = {
    new("web01", "IIS", 8L * 1024 * 1024 * 1024, StatusKind.Pass),
    new("app01", "App", 16L * 1024 * 1024 * 1024, StatusKind.Warn),
    new("db01", "SQL", 64L * 1024 * 1024 * 1024, StatusKind.Fail)
};
double[] load = { 12, 18, 31, 64, 47, 38, 55, 70, 72, 41 };

// Ui factories: short functions build each widget, so the code has the shape of the screen.
Terminal.Write(Rows(
    Columns(
        Panel("Servers", Table(servers)
            .Column(s => s.Name, c => c.WithIsKey())
            .Column(s => s.Role)
            .Column(s => s.Ram, c => c.WithPresenter(CellPresenters.Bytes))
            .Column(s => s.Status)
            .WithTotals()),
        Panel("Summary", Rows(
            KeyValue(("Servers", 3), ("Failed", 1)),
            Sparkline(load).WithLabel("CPU")))
    ).WithGap(1),
    Text("[muted]Updated 10:02[/]")
).WithGap(1));

record Server(string Name, string Role, long Ram, StatusKind Status);
Prints the frame above.

When to use which

  • One widget, configured in one place: fluent.
  • Many options, rows from a loop, or a widget you update later: object initializers.
  • A layout of panels, rows and columns: the Ui factories, or fluent if you prefer new.
  • A live dashboard: DashboardBuilder, which hands each live widget back through an out parameter for its typed handle (see Live sessions).

The fluent methods are generated at build time from each widget's public properties and compiled into ConsoleMonster.dll, so every property has one, on every target framework, with nothing generated or reflected at run time.

Edit this page on GitHub