Free & MIT-licensed terminal UI framework for .NET

Terminal UI should do more. This one clicks.

OpenTail.TUI is a reactive, Elm-architecture terminal UI framework for .NET — with real mouse support, true 24-bit color, and clickable links, running entirely inside a plain console window.

✓ MIT licensed ✓ .NET 10, AOT-ready ✓ Zero runtime dependencies
opentail-tui://widget-gallery 60fps
MOUSE Click a table row, drag a slider, click a link.
01
Table row clickedselection follows the cursor, not just ↑/↓
done
02
[docs](url) clickedMarkdownBlock link → opened in your browser
opened
03
Desktop notificationa real OS toast, from inside the terminal
sent
RENDERERDouble-buffered diff
ARCHITECTUREElm — Init/Update/View
DEPENDENCIES0 at runtime
50+widgets & building blocks
412automated tests
30fpszero-flicker diffed rendering
0runtime dependencies
Not just pretty output

A real mouse, in a real terminal.

Most .NET console libraries make text look nice. OpenTail.TUI builds interactive apps — click a row to select it, drag a slider, click a hyperlink and watch your browser actually open. All of it inside what still looks, at a glance, like a plain console window.

01

Real mouse support, not a rendering trick

Every interactive widget follows the same contract: Render draws it, HitTest/HandleMouse tells you what a click landed on — computed from the exact same math, so a click always agrees with what's on screen. Click a table row, drag a slider to a value, click a checkbox, open a dropdown.

click to selectdrag to setSGR mouse mode
02

Elm architecture, genuinely testable

Init, Update, and View are pure functions over plain C# records. Feed messages into Update in a unit test and assert on the model — no real terminal required.

record Model(int Count = 0);
public override Model Update(Model m, Msg msg)
=> msg is Msg.Increment
? m with { Count = m.Count + 1 }
: m;
public override void View(Model m, CellBuffer buf)
=> buf.Print(0, 0, $"Count: {m.Count}");
03

Zero-flicker, 30fps rendering

A double-buffered CellBuffer diffs against the last painted frame and writes only what changed. Live timers, spinners, and glowing text run smoothly without a single stray repaint.

Cell-level ANSI diffing, not a full-screen redraw
04

Reaches outside the terminal, carefully

Link.TryOpen launches a URL in the browser. Link.TryOpenFolder opens a directory in Explorer/Finder — and refuses anything that isn't an existing directory first, so it can never run a file. SystemNotification.Show pops a real OS notification.

just text output→a live OS notification
05

True 24-bit color — even real images

Every widget draws in full RGB, gracefully degrading on older terminals. ImageWidget renders an actual bitmap via the half-block technique, and can decode an uncompressed BMP file directly — no image-decoding package required.

The whole architecture, in one loop

Model. Message. Render. Repeat.

Every OpenTail.TUI screen is the same four-step loop — the same discipline that makes Elm and Bubble Tea apps easy to reason about, brought to .NET.

  1. 01
    Model

    Plain C# records hold your screen's entire state — no hidden framework fields.

  2. 02
    Update

    A pure function maps an incoming message (key, click, tick) to the next model.

  3. 03
    View

    Paint the model into a CellBuffer. The diff renderer figures out what actually changed.

  4. 04
    Ship

    AOT-compatible, zero-dependency at runtime — publish a single trimmed binary, any OS.

"Can a user click a table row?"Table.HitTest
"Can they click a hyperlink?"Link + MarkdownBlock
"Will it flicker at 30fps?"Double-buffered diffing
"Does it survive SSH and CI?"Pure ANSI, degrades safely
"Is it actually used anywhere?"Interactive dashboards & tools
The catalog, not the alphabet

Find it before you hand-roll it.

Tables, forms, overlays, effects, charts, and layout primitives — organized in WIDGETS.md by the problem each one solves, so "I need a centered modal" leads straight to the widget that already does it correctly.

TableTextInputSlider DropdownButtonMarkdownBlockLink ImageWidgetToastOverlay PasswordInputNumberInputTextAreaCheckboxList ToggleSwitchTreeFilePickerCommandMenu ProgressBarGaugeSparklineBarChartCalendar TaskListStatusBarSpinnerGlowTextRainbowText TypewriterTextPulseRevealTextMarqueeSkeleton CodeBlockExceptionViewBreadcrumbsTabs CollapsiblePanelBoxRuleViewport LogViewportLiveLogListViewScrollbar ModalHelperCanvasWidgetLayout / ConstraintGridLayout

Every widget is a stateless renderer — construct it fresh each frame with whatever data it needs. Cross-frame state (cursor position, scroll offset, selection) lives in a separate plain record your model owns, which is exactly what keeps Update unit-testable without a terminal.

Built to be actually used

Every widget answers "what did they click?"

Interactivity isn't bolted on. Widgets that own a click implement it consistently — Render and HitTest/HandleMouse share the same position math, so a click can never land somewhere different from what's drawn.

  • Click a row, it selects — not just ↑/↓
  • Drag a slider to a value with the mouse
  • Click a link, your browser opens it
  • 412 tests, including every hit-test edge case
Free, forever

MIT licensed. No paid tier.

OpenTail.TUI is a standalone open-source library — not gated behind any paid product. Use it in any .NET project.

Install from NuGet: dotnet add package OpenTail.TUI. The latest published version is 1.0.1.

FAQ

The obvious questions.

Short, direct answers — the same standard the rest of the OpenTail family holds itself to.

Isn't this just Spectre.Console?

Different focus. Spectre.Console is excellent at formatting one-shot console output — tables, prompts, progress bars. OpenTail.TUI is built for full interactive apps: an Elm-style Update/View loop, live-updating dashboards, and real mouse input, including clicking a table row or a hyperlink.

Does mouse support need a special terminal?

No special terminal, but it does need one that reports SGR mouse events — true of essentially every terminal shipped in the last decade (Windows Terminal, iTerm2, GNOME Terminal, kitty, WezTerm, ...). Where mouse input isn't available, input falls back to keyboard-only automatically.

What does it depend on?

Nothing at runtime. The only package reference in the project is MinVer, and that's a build-time-only version stamp — it never ships with your app.

Is it AOT / trimming compatible?

Yes — the project builds with IsAotCompatible set, and the native console interop uses source-generated P/Invoke (LibraryImport), not reflection.

Can I see it in a real application?

Yes — interactive dev tools, status monitors, and live dashboards use OpenTail.TUI. Check out the built-in Widget Gallery to see the mouse support, the widget catalog, and the rendering all working together.

Does it work over SSH or in CI?

Rendering works anywhere ANSI does, including over SSH. When input is redirected — piped, or running non-interactively in CI — the input loop detects that and idles safely instead of crashing.

OpenTail.TUI

Terminals got boring.
This fixes that.

On NuGet

dotnet add package OpenTail.TUI

Explore the widgets View on NuGet