Reactive extensions for Spectre.Console. Compose terminal animations with Rx and keep all UI mutations on a single, deterministic scheduler.
- Reactive wrappers for Spectre.Console
Status,Progress, andLive - Single-threaded UI scheduler to keep the console thread-safe
- Fluent, chainable context APIs
- Works on .NET Standard 2.0, .NET 8, and .NET 9
Visit https://spectreconsole.net for Spectre.Console docs and https://github.com/spectreconsole/spectre.console for the original source.
- Spectre.Console.Rx
- Spectre.Console.Rx.Json (optional JSON helpers)
Install:
# Core
dotnet add package Spectre.Console.Rx
# Optional JSON helpers
dotnet add package Spectre.Console.Rx.Json- .NET Standard 2.0
- .NET 8
- .NET 9
The console is not thread-safe. Spectre.Console.Rx enforces a single UI thread via AnsiConsoleRx.Scheduler so all reactive pipelines render on the same thread. Drive animations by pushing work to that scheduler and mark contexts finished when done.
Key points:
- Always switch to the UI scheduler:
.ObserveOn(AnsiConsoleRx.Scheduler) - Only one console context (Status/Progress/Live) should run at a time
- Call
IsFinished()on the context to end rendering/animation loops
using System.Reactive.Linq;
using Spectre.Console.Rx;
AnsiConsoleRx
.Status("[yellow]Initializing[/]", s => s.AutoRefresh(true))
.ObserveOn(AnsiConsoleRx.Scheduler)
.Subscribe(ctx => ctx.Schedule(async scheduler =>
{
await scheduler.Sleep(TimeSpan.FromMilliseconds(800));
ctx.Status("[blue]Warming up[/]");
ctx.Spinner(Spinner.Known.Dots);
await scheduler.Sleep(TimeSpan.FromMilliseconds(800));
ctx.Status("[green]Ready[/]");
ctx.IsFinished();
}));using System.Reactive.Linq;
using Spectre.Console.Rx;
var table = new Table().AddColumn("Col").AddRow("Row 1");
AnsiConsoleRx
.Live(table, ld => ld.AutoClear(false))
.ObserveOn(AnsiConsoleRx.Scheduler)
.Subscribe(ctx =>
{
// Chain updates in-place (blocking) for simple demos
ctx
.Update(250, () => table.AddRow("Row 2"))
.Update(250, () => table.AddRow("Row 3"))
.Update(250, () => table.AddRow("Done"));
ctx.IsFinished();
});using System.Reactive.Linq;
using Spectre.Console.Rx;
AnsiConsoleRx
.Progress(p => p.AutoClear(false)
.Columns(new ProgressColumn[]
{
new TaskDescriptionColumn(),
new ProgressBarColumn(),
new PercentageColumn(),
new RemainingTimeColumn(),
new SpinnerColumn(),
}))
.ObserveOn(AnsiConsoleRx.Scheduler)
.Subscribe(async ctx =>
{
var t1 = ctx.AddTask("Downloading").IsIndeterminate(false);
var t2 = ctx.AddTask("Processing");
await ctx.Schedule(TimeSpan.FromMilliseconds(100), () =>
{
t1.Increment(2);
if (t1.Value >= 100) t2.Increment(4);
});
// Progress completes automatically when all started tasks are finished
});AnsiConsoleRx.Scheduleris a single-threaded scheduler. It hosts aSynchronizationContextand processes posted work sequentially.- Use
.ObserveOn(AnsiConsoleRx.Scheduler)in your observable pipelines before mutating UI. - Use context scheduling helpers to perform timed loops without blocking your app thread.
All helpers run on the Spectre scheduler and update the UI safely.
-
Task Schedule(this IContext context, TimeSpan delay, Func<bool> isComplete, Action action)- Executes
actionrepeatedly everydelayuntilisComplete()returns true. Callscontext.Refresh()after each iteration.
- Executes
-
Task Schedule(this IContext context, Action<SpectreConsoleScheduler> action)- Executes an action on the scheduler. Useful to sequence async sleeps and UI updates.
-
Task<T> Schedule<T>(this IContext context, Func<SpectreConsoleScheduler, Task<T>> action)- Executes an async function on the scheduler and returns its result.
-
Task Schedule(this IContext context, TimeSpan delay, Action action)(ProgressContext only)- Repeats
actioneverydelayuntil all started progress tasks report finished.
- Repeats
The SpectreConsoleScheduler provides Sleep(TimeSpan) for non-blocking waits and follows IScheduler from Rx.
ISpectreConsoleScheduler Scheduler— the UI scheduler instanceIObservable<StatusContext> Status(string status, Func<Status, Status>? configure = null)IObservable<ProgressContext> Progress(Func<Progress, Progress>? configure = null)IObservable<LiveDisplayContext> Live(IRenderable renderable, Func<LiveDisplay, LiveDisplay>? configure = null)IObservable<(ProgressContext context, ProgressTask[] tasks)> AddTasks(this IObservable<ProgressContext>, Func<ProgressContext, ProgressTask[]> factory)LiveDisplayContext Update(this LiveDisplayContext ctx, int delayMs, Action action)— simple chained updates (demo-friendly)void IsFinished(this LiveDisplayContext ctx)/void IsFinished(this StatusContext ctx)— signal completion
- Properties:
string Status,Spinner Spinner,Style? SpinnerStyle,bool IsFinished - Methods:
void Refresh() - Extensions:
Status(string),Spinner(Spinner),SpinnerStyle(Style?),IsFinished()
- Properties:
bool IsFinished - Methods:
ProgressTask AddTask(...),void Refresh(),Task Schedule(TimeSpan delay, Action action)
- Properties:
bool IsFinished - Methods:
void UpdateTarget(IRenderable? target),void Refresh() - Extensions:
Update(int delayMs, Action),IsFinished()
Spectre.Console.Rx.Json adds helpers to pretty print JSON with Spectre.Console widgets.
using Spectre.Console.Rx.Json;
var panel = new Panel(new JsonText(jsonString))
.Header("Data")
.SquareBorder()
.BorderColor(Color.LightSkyBlue1);
AnsiConsole.Write(panel);- Only one Status/Progress/Live should be active at a time (the console is single-threaded)
- Always
.ObserveOn(AnsiConsoleRx.Scheduler)before mutating UI - End animations with
IsFinished()so observables complete - Prefer scheduler
SleepoverThread.Sleepto keep the pump responsive - Keep heavy work off the UI scheduler; compute elsewhere, then push results to UI via
ObserveOn
- Nothing renders / pipeline hangs
- Ensure
.ObserveOn(AnsiConsoleRx.Scheduler)is applied beforeSubscribethat updates UI - Make sure you call
IsFinished()on the context when done
- Ensure
- Flicker or interleaved output
- Avoid running multiple contexts concurrently; sequence them
- Exceptions swallowed
- Wrap your subscription in
Subscribe(onNext, onError, onCompleted)and logonError
- Wrap your subscription in
See example projects under src:
LiveExample,LiveTableExampleStatusExampleProgressExampleCombinedExample
MIT License. See the LICENSE file for details.