OpenTail.TUI
1.0.1
Prefix Reserved
dotnet add package OpenTail.TUI --version 1.0.1
NuGet\Install-Package OpenTail.TUI -Version 1.0.1
<PackageReference Include="OpenTail.TUI" Version="1.0.1" />
<PackageVersion Include="OpenTail.TUI" Version="1.0.1" />
<PackageReference Include="OpenTail.TUI" />
paket add OpenTail.TUI --version 1.0.1
#r "nuget: OpenTail.TUI, 1.0.1"
#:package OpenTail.TUI@1.0.1
#addin nuget:?package=OpenTail.TUI&version=1.0.1
#tool nuget:?package=OpenTail.TUI&version=1.0.1
OpenTail.TUI
A modern, reactive, Elm-architecture terminal UI framework for .NET.
Building Terminal User Interfaces (TUIs) in C# has historically involved fighting with the console state, wrestling with partial screen redraws that cause flickering, or adopting heavy event-driven paradigms that feel out of place for modern, dynamic dashboards.
OpenTail.TUI takes a completely different approach. Heavily inspired by Elm and Rust's ratatui, it brings a strict, unidirectional data flow and a pristine double-buffered rendering engine to the .NET ecosystem. It is designed to let you build rich, 30fps animated terminal dashboards without ever worrying about cursor positions or thread locking.
Installation
dotnet add package OpenTail.TUI
Targets .NET 10 and is AOT-compatible.
Features
♻️ Elm Architecture: Strict unidirectional flow. You define your
Init, yourUpdate(which handles messages and returns a new state), and yourView(which renders the state).🎞️ Zero-Flicker Animations: A highly optimized
CellBufferdiffs your UI against the terminal's current state and only writes the changes. It supports smooth 30fps periodic rendering so you can easily implement live timers, spinners, and glowing text.📦 Component-Driven Widgets: Includes a suite of 40+ layout tools and widgets out of the box:
- Layout & Structure:
Box,Panel,Tabs,ModalHelper,Layout/Constraint - Data Visualization:
Table(with virtualized scrolling & dynamic borders),Tree,Sparkline,BarChart,Calendar - Formatting & Text:
Paragraph(word-wrapping & truncation),MarkdownBlock,CodeBlock,ExceptionView,Rule - Inputs & Navigation:
TextInput,PasswordInput,NumberInput,TextArea,CheckboxList,DropdownButton/DropdownOverlay,ToggleSwitch,Slider,Scrollbar,Breadcrumbs,FilePicker,CommandMenu - Feedback & Overlays:
ToastOverlay(Z-ordered notifications),Skeleton(animated loading placeholders),ProgressBar,Gauge - Live/Scrolling Content:
Viewport,LogViewport,LiveLog(auto-following, bottom-anchored scrollback for streaming output) - Animations & Effects:
Marquee,RainbowText,GlowText,PulseRevealText,Spinner,CountdownLine - Terminal Graphics:
CanvasWidget(Braille rendering),ImageWidget(TrueColor half-block rendering)
See WIDGETS.md for the full catalog, organized by the problem each widget solves rather than alphabetically.
- Layout & Structure:
🎨 Rich Styling: First-class support for
Colorstructures andStylecomponents, gracefully degrading to standard console colors if modern virtual terminal sequences are unsupported.🧵 Thread Safe:
Published<T>gives a background task a lock-free way to hand live progress to the render loop, so a long-running operation never needs a hand-rolled lock around state the view also reads.
Quick Start
using OpenTail.TUI.Runtime;
using OpenTail.TUI.Core;
using OpenTail.TUI.Widgets;
public sealed record TickMsg(DateTime Time) : IMsg;
public sealed class MyCounterApp : IModel
{
private int _count;
private double _phase;
private DateTime? _lastTick;
// Kick off a 30fps animation loop as soon as the app starts.
public Cmd? Init() => Cmds.Tick(TimeSpan.FromMilliseconds(33), t => new TickMsg(t));
public (IModel Model, Cmd? Cmd) Update(IMsg msg)
{
switch (msg)
{
case KeyMsg { Key: ConsoleKey.Spacebar }:
_count++;
return (this, null);
case KeyMsg { Key: ConsoleKey.Escape }:
return (this, Cmds.Quit());
case TickMsg tick:
if (_lastTick is { } last)
_phase += (tick.Time - last).TotalSeconds;
_lastTick = tick.Time;
return (this, Cmds.Tick(TimeSpan.FromMilliseconds(33), t => new TickMsg(t)));
default:
return (this, null);
}
}
public void View(Rect area, CellBuffer buffer)
{
buffer.Clear();
var boxArea = new Rect(area.X + 2, area.Y + 2, 40, 10);
new Box("Hello TUI", Theme.Dark.Primary, Theme.Dark.Text).Render(boxArea, buffer);
var inner = Box.Inner(boxArea);
// A glowing text animation that pulses based on elapsed time (_phase).
new GlowText($"Count: {_count}", _phase, Theme.Dark.Muted.Foreground!.Value, Theme.Dark.Accent.Foreground!.Value, speed: 3.0)
.Render(new Rect(inner.X + 2, inner.Y + 2, inner.Width - 4, 1), buffer);
buffer.SetText(inner.X + 2, inner.Y + 4, "Press SPACE to increment, ESC to quit", Theme.Dark.Muted, inner.Width - 4);
}
}
Run it with TuiApp, which owns the terminal, the render loop, and input handling:
await new TuiApp(new MyCounterApp()).RunAsync();
Want to see more widgets in action first? Clone the repo and run the bundled demo:
dotnet run --project Demo
Architecture
IModel: The single source of truth for your screen's state.Update(IMsg msg): The only place state changes occur. When a key is pressed, a background task finishes, or a timer ticks, it yields anIMsg. The framework passes it here, you return the new state and optionally aCmdto execute a side-effect.View(Rect area, CellBuffer buffer): A pure function. It takes the bounds of the terminal and a blank canvas, and you instruct your widgets to draw themselves. The framework handles diffing this buffer against the real terminal window to apply changes instantly with zero tearing.
Widgets themselves are stateless renderers — construct one fresh per frame with whatever data/style it needs. Where a widget needs state across frames (a text cursor, a scroll offset), that state is a separate readonly record struct your model owns and mutates in Update (e.g. TextInputState, ViewportState, LiveLogState), which is what keeps Update itself unit-testable without a real terminal.
License
MIT — see the LICENSE file for details.
OpenTail.TUI was originally built to power FlightDeck, a real-time testing dashboard for agentic AI workflows, before being extracted as a standalone package.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.