TUIKit 0.10.1
See the version list below for details.
dotnet add package TUIKit --version 0.10.1
NuGet\Install-Package TUIKit -Version 0.10.1
<PackageReference Include="TUIKit" Version="0.10.1" />
<PackageVersion Include="TUIKit" Version="0.10.1" />
<PackageReference Include="TUIKit" />
paket add TUIKit --version 0.10.1
#r "nuget: TUIKit, 0.10.1"
#:package TUIKit@0.10.1
#addin nuget:?package=TUIKit&version=0.10.1
#tool nuget:?package=TUIKit&version=0.10.1
<div align="center"> <img src="assets/logo.png" alt="TUIKit" width="160" height="160" /> </div>
TUIKit
A concurrent, high-performance terminal UI framework for .NET. TUIKit lets you drop a multi-pane, live-updating interface into an ordinary console application — the kind of surface an AI agent harness needs: a streaming transcript on one side, tool output and telemetry on another, an input composer at the bottom, and modal dialogs on top of it all.
v0.10.1 — Alpha. An early public preview. The API and capabilities are subject to change. It is usable and extensively tested, but treat it as pre-1.0: pin your version and expect breaking changes between minor releases until it stabilizes. v0.10.1 is a small robustness fix: the terminal-restore flush on the process-exit/teardown path no longer throws if stdout is already disposed or closed for writing when an app exits without a clean stop (the exit handler now swallows
ObjectDisposedException/NotSupportedExceptionalongsideIOException). v0.10.0 delivers full mouse support: hover via any-motion tracking (on by default, with per-frame move coalescing), host-synthesizedEnter/Leaveevents routed through the existingIMouseAwareinterface, aMouseTrackingModeescape hatch, horizontal wheel (WheelLeft/WheelRight), terminal focus reporting (TerminalFocusChanged), host-stamped single/double/tripleClickCount, link hover (LinkHoveredfor status-bar URL previews), hover styles and click activation onTabView,MenuBar,ListView<T>,Tree<T>, andCheckbox, and a conhost QuickEdit fix so legacy Windows consoles stop swallowing mouse input — see the mouse support matrix for exactly what works where. The guided tour gains a Mouse playground page. v0.9.0 fixed bracketed paste into focused input: pasting into a prompt or inline add field (an Access key, Secret key, password, or token) was silently dropped because the paste reached only application-global handlers and never the focus-trapping modal that owns the field. Paste routes like keys do — active modal first, then the globalPasteReceivedfallback — via theModal.HandlePaste/ModalStack.HandlePastehooks andTextField.Insert, which drops control characters so a newline-terminated clipboard payload collapses onto one line instead of submitting. v0.8.4 addsListEditorModal<T>(a validated, single-screen editor for an ordered list — inline add with live preview and validation, remove, optional reorder),CheckTree<T>andFileSelectModal(cascading tri-state folder/file selection that pre-seeds and reveals a saved selection and returns top-most includes plus excluded holes), and aFileBrowser.SelectionModeflag for flat multi-select — and fixes twoTree<T>bugs that bit any large tree (per-render child enumeration, now cached behind an optional cheaphasChildrenprobe; and identity-keyed expansion, now keyed through an optional comparer). v0.8.3 completes page and jump navigation so every navigable list and scroll widget responds to the same keys: PageUp/PageDown and Home/End work inListView(and so inSelectAsync/SelectModal,ActionListView,ReorderableList),CheckList/MultiSelectModal,FuzzyList,DataTable,Tree,FileBrowser,KeyBindingEditor, andAutocompleteOverlay; Home/End jump to the top/bottom inScrollViewandDiffView; andRadioGroupand theMenuBardrop-down gain Home/End. v0.8.1 hardened terminal restore on exit: a Ctrl+C or unhandled exception no longer leaves the shell with mouse reporting on (a scrolling wheel spewing^[[<…M) or, on Windows, in raw input mode (arrow keys echoing^[[A) — the host installs a cross-platformConsole.CancelKeyPress+AppDomain.ProcessExitsafety net that restores the terminal on every exit path. The 0.8 line adds a text-to-ASCII-art font engine (TUIKit.Ascii):AsciiArt.Renderwith faithful FIGlet layout (full-width, kerning, and the six horizontal smushing rules), a thread-safeAsciiFontLibrarymanager whoseDefaultships 84 built-in fonts, aFigletFontLoaderfor your own.flf/.tlffiles, and theAsciiArtTextwidget — all additive alongside the existingBanner/BannerText. See the changelog.
Quick links: Building Terminal Apps guide · Runnable example · Changelog · Contributing
See it live in ~30 seconds — a self-describing guided tour of every feature, with the code beside each one:
dotnet run --project src/TUIKit.Example # guided tour dotnet run --project src/TUIKit.Example -- --contract # the interaction-contract demo
<details> <summary><strong>Screenshots</strong></summary>
<br />
<a href="assets/ss1.png"><img src="assets/ss1.png" alt="Screenshot 1" width="100%" /></a>
<a href="assets/ss2.png"><img src="assets/ss2.png" alt="Screenshot 2" width="100%" /></a>
<a href="assets/ss3.png"><img src="assets/ss3.png" alt="Screenshot 3" width="100%" /></a>
<a href="assets/ss4.png"><img src="assets/ss4.png" alt="Screenshot 4" width="100%" /></a>
</details>
What it is
TUIKit is a library, not an application. You reference it, describe a layout as a set of rectangles, bind panes to those rectangles, register some keybindings, and hand control to a host that owns the render and input loops. It is built for the case where several threads write at once: a background worker can call pane.WriteLine(...) while the render thread repaints, and TUIKit handles the ordering, the diffing, and the minimization of escape sequences for you.
It multi-targets netstandard2.0, net8.0, and net10.0. The modern targets are dependency-free; netstandard2.0 pulls in a small compatibility shim so the same code runs on .NET Framework, Mono, and Unity.
What it does
- Developer-defined regions. Declare any number of rectangles, each with its own resize behavior — fixed, edge-anchored, stretch, or proportional — plus per-rectangle padding and an optional background (an explicit color or a named theme role, so a sidebar or status strip is tinted and restyles with the theme). TUIKit reflows them when the window changes and shows a "terminal too small" screen when it can't fit.
- Thread-safe, mutable content. Any thread may write to any pane; writes are FIFO per pane. Lines can be updated in place, so a tool call goes
running…→done (1.2s)and a progress bar advances without redrawing the world. - Streaming with a smart scroll lock. Scroll up to detach from the live tail; return to the bottom to re-attach. A
↓ N newindicator tells you what you're missing. - Rich text. A fluent styled-text builder, inline markup (
[bold red]…[/]), a Markdown renderer (headings, lists, task lists, tables, blockquotes, code), word/character wrapping, and correct Unicode column width for CJK, combining marks, and emoji grapheme clusters. - Enhanced input. A byte decoder for UTF-8, control keys, arrows, function keys, the Kitty/CSI-u protocol, SGR mouse, and bracketed paste — routed through a central command table with scopes, multi-key chords (
Ctrl+K Ctrl+T), and a configurable Ctrl+C policy. Carriage return (Enter) and line feed (Ctrl+J) decode distinctly, so you can bindCtrl+Jas a newline chord that works even where the terminal can't reportShift+Enter. - Mouse, links, and selection. Full pointer support: click-to-focus, single/double/triple click synthesis, drag, vertical and horizontal wheel, and hover — any-motion tracking (on by default, with per-frame move coalescing) delivers
Enter/Leaveevents synthesized from a per-frame hit-test map, so widgets highlight under the pointer. Terminal focus reporting (TerminalFocusChanged) lets an app dim itself when the window blurs. Virtual links get per-frame hit-testing, a security allowlist for auto-linkification, hover tracking (LinkHoveredfor status-bar URL previews), OSC 8 hyperlink emission, and keyboard link hints; text selection and OSC 52 clipboard copy work over SSH. A one-key toggle hands the mouse back to the terminal for native drag-select, andMouseTrackingModedrops to drag-only or off for chatty links. See Mouse support by environment for the exact matrix. - A host-owned interaction contract. The host wires the interactive skeleton for you: a focus ring across bound focusable widgets (
Focus,FocusNext/FocusPrevious,FocusChanged,Tabtraversal,FocusContextthat follows focus), an explicit key-precedence chain (modal → pre-filter → focus-scoped commands → focused-widget first refusal → global commands → fallback), click-to-focus and wheel routing from a per-frame hit-test map, and application-shell dock layout helpers (DockTop/DockBottom/DockLeft/DockRight/Fill). It's all additive — the rawKeyReceived/MouseReceived/RenderOverlayhooks still work. - Modals, notifications, and prompts. A focus-trapping modal stack with awaitable, typed results (
ShowAsync<T>, plusConfirmAsync/PromptAsync/SelectAsync), a reusableDialogModalbase that auto-sizes a centered box with a title and footer hint so custom dialogs stop hand-rolling geometry, aMultiSelectModal<T>for choosing several options, aPost(Action)loop scheduler for marshalling continuations back onto the UI thread, non-focus-stealing toasts, and a global focus manager forTaborder. - A broad widget toolkit. Inputs (text field with optional character masking for secret entry such as passwords and tokens, multi-line editor with undo and a kill ring, checkbox, radio group, forms); selection (
CheckList<T>multi-select, sortable virtualizedDataTable<T>, tree, tabs, fuzzy finder, list); navigation (menu bar, file browser, scroll view, collapsible section, status bar); status and feedback (aDefinitionListlabeled-value panel,ActivityIndicatorworking line, gauge, sparkline, progress bar, spinner, concurrent multi-task progress); aRuledivider; plus a user-editable key-binding editor. - Selection and editing lists. Generic
ListView<T>andFuzzyList<T>return the selected object (not a string),ActionListView<T>gives rows keyboard actions with a typed result, andReorderableList<T>moves and removes items in place. - Command surfaces and typeahead. A
CommandRegistrydrives key bindings, a grouped menu bar, a fuzzy command palette, and a/slashrouter from one command list, and anAutocompleteOverlay(with a pluggableISuggestionProvider) shows caret-anchored suggestions for any text input. - Streaming and text helpers. A
StreamingTranscriptthat projects streamed text and keyed in-place status lines onto a pane (finalizing each block as Markdown), plusHintTextfooter wrapping,ColumnFormattercolumn alignment, and aSubmitKeyResolverthat settles the cross-terminal Enter-vs-newline question for multi-line editors. - Charts, diffs, and images. Braille line and bar charts, a diff viewer with syntax highlighting, FIGlet-style banners, a color picker, and image rendering — half-block on any terminal, sixel or kitty where supported.
- Text-to-ASCII-art. A font engine (
TUIKit.Ascii) that turns text into large multi-row art with faithful FIGlet layout — full-width, kerning, and the six horizontal smushing rules.AsciiFontLibrary.Defaultships 84 built-in fonts (Standard, Slant, the Small family, Doom, Colossal, ANSI Shadow, Sub-Zero, and more);AsciiArtTextdrops any of them into a layout, andFigletFontLoaderloads your own.flf/.tlffiles. Fonts with restrictive licensing are not bundled. - Reactive and animated. Thread-safe
Observable<T>one-way data binding, and deterministic, tick-driven animation (Easing,Tween,FrameTimer) that replays identically in tests. - Theming and diagnostics. Dark, light, and high-contrast themes with an ASCII-border fallback; a debug overlay; frame statistics; and input record/replay.
- Headless rendering. Render to an in-memory cell buffer and assert it as text. It's how TUIKit tests itself, and it's a shipped feature so you can snapshot-test your own UI.
Why use it
Most console UI libraries assume a single-threaded, immediate-mode loop and a rigid split layout. An agent harness breaks both assumptions. Output arrives in a flood of tokens from one thread while tool calls mutate their status lines from another, the operator scrolls back through history without losing the live tail, and a confirmation dialog can appear at any moment.
TUIKit is designed around that reality. Panes are retained objects that own their state, so a background thread writing to one is natural rather than a special case. Rendering is a double-buffered diff that emits only the cells that changed and coalesces styling, so a 100 Hz token stream doesn't turn into 100 full repaints. And because the whole thing renders into an in-memory buffer, you can test your interface deterministically instead of eyeballing a terminal.
If you are building a chat client, an agent control panel, a log viewer, a deployment dashboard, or any long-running console tool where content moves on its own, TUIKit gives you the concurrency model and the rendering discipline to do it without reinventing them.
How it compares
- vs. Spectre.Console: Spectre excels at rich one-shot output — tables, prompts, and progress in a linear program. TUIKit is a retained, concurrent, full-screen framework: panes are long-lived objects that many threads write to while a diffing renderer repaints, which is what a live dashboard or agent harness needs.
- vs. Terminal.Gui: Terminal.Gui is a classic desktop-style widget toolkit (windows, menus, dialogs). TUIKit shares much of that toolkit but is oriented toward streaming content and headless snapshot testing — you render into an in-memory buffer and assert it as text, so your UI is unit-testable rather than eyeballed.
- Dependency-free on modern targets, multi-targeting
netstandard2.0/net8.0/net10.0, and self-contained Unicode/width handling (noSystem.Text-heavy detours, no native deps).
How it works
TUIKit is a stack of small, testable layers. Each one is useful on its own and none of them reach into the internals of the layer above.
- Terminal backend (
ITerminalBackend) — the raw sink for output bytes and source for input bytes.ConsoleBackenddrives a real terminal (VT enabled viaSetConsoleModeon Windows, in-processtermiosraw mode on Unix).HeadlessBackendcaptures everything in memory for tests. - Renderer (
TerminalRenderer) — composes a frame into a back buffer, diffs it against what's on screen, and emits the minimal set of escape sequences to reconcile them. Truecolor is quantized to 256/16 colors when the terminal can't do better. - Layout (
Layout,Region) — resolves each region's rectangle from its constraints and padding for the current surface size, and derives the minimum size below which it shows the block screen. - Content (
Pane) — a thread-safe, scrolling, mutable text surface with a capped ring buffer, mutable line handles, and the smart scroll lock. - Input (
InputParser,CommandRoutingTable) — decodes raw bytes into key, mouse, and paste events and routes them by scope, honoring multi-key chords and the Ctrl+C policy. - Host (
TuiApplication) — ties it together: it owns the render and input loops, drives the focus ring and the key-precedence chain, hit-tests the mouse for click-to-focus, wheel routing, and hover Enter/Leave synthesis (with move coalescing so any-motion tracking stays cheap), stamps multi-click counts on presses, dispatches commands and terminal focus changes, manages the modal stack and notifications, restores the terminal on exit, and degrades to plain line output when stdout isn't a TTY.
Installation
dotnet add package TUIKit
Or add it to your project file:
<PackageReference Include="TUIKit" Version="0.10.1" />
Quick start
A two-pane app — a scrolling log above a prompt line — with a background thread streaming into it and Ctrl+Q to quit. One call (TuiApp.RunAsync) owns the terminal, the render loop, and the input loop:
using System.Threading;
using System.Threading.Tasks;
using TUIKit;
using TUIKit.Content;
using TUIKit.Hosting;
await TuiApp.RunAsync(app =>
{
// Two rectangles: a log that fills the space above a 3-row prompt.
Pane log = app.AddPane("log", r => r.FillWidth().FillHeight(0, 3));
app.AddPane("prompt", r => r.FillWidth().BottomAnchored(0, 3));
// Bind a chord straight to an action.
app.Bind("Ctrl+Q", app.Quit);
// Any thread may write to a pane; ordering is FIFO per pane.
_ = Task.Run(async () =>
{
for (int i = 1; i <= 100; i++)
{
log.WriteLine(Text.From($"event {i}").Green());
await Task.Delay(50);
}
});
},
CancellationToken.None);
Prefer to wire things up by hand? Construct a ConsoleBackend and a TuiApplication, set app.Layout, BindPane, register commands, and await app.RunAsync(...) yourself — the Building Terminal Apps guide shows both paths.
Example application
A complete, runnable demo lives in src/TUIKit.Example — a simulated agent control harness that exercises every major capability against a fake agent (no network, no model), so it is deterministic and self-contained. Its README carries a capability-coverage matrix mapping each library feature to the exact interaction that demonstrates it.
Walkthrough
Run it.
dotnet run --project src/TUIKit.ExampleYou land in a full-screen harness: a header bar, a streaming transcript on the left, a tool panel and live telemetry on the right, a bordered composer along the bottom, and a footer of shortcuts.
Watch it stream. The simulated agent writes Markdown tokens into the transcript from a background thread — headings, bold, lists, a block quote, a fenced code block, and a link — while a tool call runs and its status line mutates from
runningtodone (0.9s)in place. The telemetry panel updates a gauge, a sparkline, a progress bar, and a table every frame.Press
F1(or?). A help overlay lists every keybinding. The demo documents itself.Type into the composer and press
Enter. Your message is echoed into the transcript.Alt+Enterinserts a newline; the composer is a full multi-line editor with undo/redo (Ctrl+Z/Ctrl+Y) and a kill ring (Ctrl+K/Ctrl+U).Scroll with
PageUp/PageDown. Scrolling up detaches the transcript from the live tail — the footer showsdetached N new— and returning to the bottom re-attaches it. The mouse wheel scrolls whichever pane is under the cursor.Open the command palette with
Ctrl+P. A list widget in a modal; choose an action with the arrow keys andEnter.Ctrl+Gopens a settings form with a radio group, a checkbox, and a text field, withTabmoving focus between them.Ctrl+Lraises a confirmation dialog ("the agent wants to runrm -rf build/") whose result drives a toast.Cycle the theme with
Ctrl+K Ctrl+T(a two-key chord) and toggle the debug overlay withCtrl+Dto see every region's outline and the frame timing. High-contrast mode switches borders to ASCII.Quit.
Ctrl+Qexits cleanly and restores your terminal.Ctrl+Cis configured to require a double-tap.
Headless and non-interactive modes
The example renders a single frame to text without a terminal, which is how you would snapshot a UI in CI:
dotnet run --project src/TUIKit.Example -- --once # print one frame to stdout
dotnet run --project src/TUIKit.Example -- --once --debug # ... with the debug overlay
dotnet run --project src/TUIKit.Example -- --contract-once # the interaction-contract demo frame
dotnet run --project src/TUIKit.Example | cat # non-TTY -> plain line output
The interaction-contract demo (--contract) is the shortest path to seeing the host at work: a four-way dock shell (header, sidebar, editor, footer) built from real regions, a focus ring you drive with Tab or the mouse, a focus-scoped Enter that opens a file in the sidebar while Enter in the editor inserts a newline, a two-key theme chord, and a typed picker modal marshalled back onto the loop with Post — the whole app in ~120 lines of ContractDemo.cs.
Terminal support
Interactive keyboard, rendering, and terminal restoration have been tested and validated on Windows (Windows Terminal), macOS (iTerm2), and Linux, including over an SSH session — the same raw-mode input path (native SetConsoleMode on Windows, libc termios on Unix) behaves identically across all three.
Tier-1, intended targets are Windows Terminal, iTerm2, Ghostty, WezTerm, Alacritty, and kitty — including over SSH and inside tmux. Terminals that can't report enhanced keys or truecolor (macOS Terminal.app, legacy conhost, PuTTY) run in a degraded mode with capability reporting rather than failing. When stdout is not a TTY, TUIKit emits plain line output instead of escape sequences.
Mouse support by environment
TUIKit speaks one mouse dialect everywhere — SGR (DECSET 1006) extended reporting with button
tracking (1000), drag motion (1002), any-motion hover (1003), and focus reporting (1004). On
Windows, ENABLE_VIRTUAL_TERMINAL_INPUT makes the console translate native mouse input into the
same escape sequences a Unix terminal emits, so a single parser serves every platform. Modes a
terminal lacks are silently ignored, so every ⚠️/❌ below degrades gracefully — the app keeps
running and that feature simply doesn't fire.
| Environment | Click / drag / wheel | Hover / any-motion | Horizontal wheel | Focus in/out | Coords > 223 cols | Notes |
|---|---|---|---|---|---|---|
| Windows Terminal (Win 10/11) | ✅ | ✅ | ✅ | ✅ | ✅ | Primary Windows target; full VT input translation. |
| Legacy conhost (Win 10+) | ⚠️ | ⚠️ | ❌ | ❌ | ✅ | VT mouse translation is partial and version-dependent; TUIKit clears QuickEdit so events aren't swallowed by selection mode. Pre-VT conhost (Win 8.1 and earlier) gets no mouse at all. |
| macOS Terminal.app | ✅ | ⚠️ | ❌ | ⚠️ | ✅ | Drag tracking works; any-motion and focus reporting vary by macOS version — hover degrades to drag-only where 1003 is ignored. |
| iTerm2 | ✅ | ✅ | ✅ | ✅ | ✅ | Full support. |
| kitty / Alacritty / WezTerm / Ghostty | ✅ | ✅ | ✅ | ✅ | ✅ | Full support. |
| VTE terminals (GNOME Terminal, Tilix, xfce4-terminal) | ✅ | ✅ | ✅ | ✅ | ✅ | Full support. |
| xterm | ✅ | ✅ | ✅ | ✅ | ✅ | Reference implementation of every mode used. |
tmux (set -g mouse on) |
✅ | ✅ | ⚠️ | ✅ | ✅ | Requires mouse enabled in tmux; tmux consumes some events for its own panes; horizontal-wheel forwarding depends on tmux version. With mouse off, no events reach the app. |
| GNU screen | ⚠️ | ❌ | ❌ | ❌ | ⚠️ | screen's pass-through is limited to basic tracking; treat as keyboard-first. |
| SSH (any client) | — | — | — | — | — | Transparent: capability is that of the client terminal emulator — the rows above apply to whatever the user runs locally. |
| WSL | — | — | — | — | — | Transparent: capability is that of the hosting console (usually Windows Terminal → full support). |
| Headless / redirected / CI | ❌ (by design) | ❌ | ❌ | ❌ | — | IsInteractive is false; no escape sequences are emitted. Tests inject synthetic events instead. |
Deliberately out of scope, and why:
| Capability | Why not |
|---|---|
| Pixel-precision coordinates (DECSET 1016) | Terminal support is spotty and TUIKit's rendering model is a cell grid; cell granularity is the reliable cross-platform contract. |
| Legacy mouse encodings (X10, UTF-8 1005, urxvt 1015) | SGR 1006 is ubiquitous in every terminal that reports the mouse at all today; the legacy encodings add ambiguity and coordinate-ceiling bugs for terminals that effectively no longer exist. Terminals without SGR fall back to keyboard-only operation via TerminalCapabilities.SgrMouse. |
| Mouse events outside the terminal window / global position | No terminal protocol reports the pointer outside the window. Leave is synthesized for transitions between widgets and out of bound regions. |
| Pointer cursor shape changes on hover | No standardized escape sequence with wide enough support to build API on. |
| Pre-Windows-10 consoles | No ENABLE_VIRTUAL_TERMINAL_INPUT, so no VT mouse translation exists to consume. |
Building and testing
dotnet build src/TUIKit.sln
# Console test runner (colored, tabular output; exit code 0/1)
dotnet run --project src/Test.Automated
dotnet run --project src/Test.Automated -- --results results.json
# The same test descriptors through xUnit and NUnit
dotnet test src/Test.Xunit
dotnet test src/Test.Nunit
Tests are written with Touchstone: one set of descriptors in Test.Shared runs identically through the console runner, xUnit, and NUnit. See docs/SURFACE_COVERAGE.md for the coverage audit.
Project status
Alpha. The core — plus the full widget, layout, reactive, animation, testing, and terminal-integration surface — is implemented and covered by an extensive suite of Touchstone cases that run identically through the console, xUnit, and NUnit runners on net8.0 and net10.0 (363 cases in the console runner as of 0.6.0). The 0.6 line added per-region background colors and a batch of horizontal components — a DialogModal base, CheckList<T>/MultiSelectModal<T>, generic ListView<T>/FuzzyList<T>, ActionListView<T>, ReorderableList<T>, DefinitionList, ActivityIndicator, StreamingTranscript, a CommandRegistry, focus-following ScrollView, and small text/input utilities — and shipped autocomplete/typeahead (AutocompleteOverlay), the one capability the original build plan had held back, so every catalogued capability now ships. The host owns an interaction contract: a focus ring, an explicit key-precedence chain with focused-widget first refusal, mouse hit-testing for click-to-focus and wheel routing, typed modals, and application-shell dock helpers — so a standard interactive app is "bind widgets, set focus, run." The 0.8 line adds a text-to-ASCII-art font engine (TUIKit.Ascii): AsciiArt.Render with faithful FIGlet layout (full-width, kerning, and the six horizontal smushing rules), a thread-safe AsciiFontLibrary manager whose Default ships 84 built-in fonts, a FigletFontLoader for .flf/.tlf files, and the AsciiArtText widget — with per-font attribution bundled and a licensing gate that would exclude any restrictive font; v0.8.1 hardens terminal restore on exit so Ctrl+C or an unhandled exception can no longer leave the shell with mouse reporting or raw input mode enabled, and v0.8.2–0.8.3 add uniform PageUp/PageDown and Home/End navigation across every list and scroll widget; v0.9.0 fixes bracketed paste into a focused prompt or inline add field so an Access key, Secret key, password, or token pastes instead of being silently dropped; and v0.10.0 completes mouse support — hover with synthesized Enter/Leave, tracking-mode control, horizontal wheel, terminal focus reporting, host-stamped multi-click counts, link hover, widget hover styles, and the conhost QuickEdit fix (504 console cases as of 0.10.0). Still outstanding: a benchmark suite. The platform-specific ConsoleBackend and the interactive run loop are validated by manual smoke testing rather than headless tests, and have been confirmed working on Windows, macOS, and Linux, including over SSH. See CHANGELOG.md and archive/TUIKIT_PLAN.md for detail.
Contributing, issues, and discussions
Bug reports, feature requests, and questions are all welcome on GitHub:
- File a bug or request a feature: open an issue at github.com/jchristn/TUIKit/issues. For a bug, include your OS, terminal, target framework, and the smallest snippet that reproduces it — a headless snapshot (
Snapshot.ToText) of the misbehaving frame is ideal. - Start a discussion or propose a direction: use github.com/jchristn/TUIKit/discussions for design questions, ideas, and "should this work like X?" conversations before a PR.
- Pull requests: please open an issue or discussion first for anything non-trivial so the API direction can be agreed on while it's still alpha. Match the existing code style (documented in
CLAUDE.md) and add Touchstone descriptors for new behavior.
License
TUIKit is released under the MIT License. Copyright (c) 2026 Joel Christner.
Logo Attribution
The TUIKit logo is composed from the following sources:
- Terminalicon2 — Wikimedia Commons
- Toolkit icon — Flaticon
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. 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. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Microsoft.Bcl.AsyncInterfaces (>= 10.0.11)
- System.Memory (>= 4.6.3)
- System.Threading.Channels (>= 10.0.11)
-
net10.0
- No dependencies.
-
net8.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.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.12.0 | 0 | 9/12/2026 |
| 0.11.0 | 0 | 9/12/2026 |
| 0.10.3 | 79 | 9/11/2026 |
| 0.10.1 | 222 | 9/3/2026 |
| 0.10.0 | 94 | 9/3/2026 |
| 0.9.0 | 152 | 8/29/2026 |
| 0.8.4 | 142 | 8/24/2026 |
| 0.8.3 | 118 | 8/21/2026 |
| 0.8.2 | 118 | 8/21/2026 |
| 0.8.1 | 99 | 8/21/2026 |
| 0.8.0 | 176 | 8/19/2026 |
| 0.6.1 | 145 | 8/16/2026 |
| 0.6.0 | 113 | 8/13/2026 |
| 0.5.1 | 155 | 8/4/2026 |
| 0.5.0 | 118 | 8/4/2026 |
| 0.4.1 | 187 | 7/30/2026 |
| 0.4.0 | 135 | 7/30/2026 |
| 0.3.1 | 126 | 7/29/2026 |
| 0.2.0 | 119 | 7/29/2026 |
v0.10.1 (Alpha) — exit-path teardown hardening. A process-exit handler must never throw. The final terminal-restore Flush() on the ProcessExit/teardown path (TuiApplication.Teardown and ConsoleBackend.Stop) previously caught only IOException, so a stdout stream that was already disposed (ObjectDisposedException) or closed for writing (NotSupportedException) when the process exited without a clean Stop would let the exception escape the exit handler. Both teardown catches now also swallow ObjectDisposedException and NotSupportedException, so an app that exits without a clean stop can no longer crash from the fallback restore path. Fallback-path robustness only: correct usage (a clean Stop/Dispose while stdout is open) was and is unaffected, and normal-operation Flush calls still propagate errors. No API changes; additive and backward compatible. Prior release — v0.10.0 (Alpha) — full mouse support. Completes the pointer story: hover via any-motion tracking (DECSET 1003, on by default, with per-frame move coalescing), host-synthesized MouseEventKind.Enter/Leave delivered through the existing IMouseAware interface in a documented order (Leave old, Enter new, then the triggering event), a MouseTrackingMode property (None/ButtonsAndDrag/AnyMotion) that rewrites terminal modes live, horizontal wheel (MouseButton.WheelLeft/WheelRight from SGR buttons 66/67, mapped to horizontal scrolling in ScrollView), terminal focus reporting (DECSET 1004 → TerminalFocusChanged; focus loss clears hover), host-stamped single/double/triple ClickCount on presses via ClickSynthesizer (new PositionSlopCells), link hover (TuiApplication.Links/HoveredLink/LinkHovered for status-bar URL previews), and hover visual states plus click activation on a representative widget set — TabView, MenuBar, ListView<T>, Tree<T>, Checkbox — each with a configurable HoverStyle. Fixes: legacy conhost QuickEdit no longer swallows mouse input (ENABLE_QUICK_EDIT_MODE cleared with ENABLE_EXTENDED_FLAGS, restored on exit); SGR wheel buttons 66/67 previously mis-decoded as WheelUp/WheelDown; the parser now drops SGR reports with a bad final byte, missing parameters, or zero wire coordinates instead of emitting bogus events. TerminalCapabilities gains AnyMotionMouse and FocusReporting flags (constructor gained two parameters); MouseEventKind, MouseButton, and InputEventKind gained members, so consumer switches without a default arm will see new values; unconsumed hover moves now flow to MouseReceived, a higher-volume stream. Unsupported modes are ignored by terminals, so everything degrades gracefully (see the README mouse-support matrix). The guided tour gains a Mouse playground page. 44 new Touchstone cases across five new suites (504 total across console/xUnit/NUnit on net8.0/net10.0). Prior release — v0.9.0 (Alpha) — bracketed paste reaches focused input. Fixes pasting into a prompt or inline add field: a bracketed paste was decoded into a single paste event and then dropped, because only application-global PasteReceived handlers were offered the event while the focus-trapping modal stack (which owns the text field during a prompt) never saw it — so typing worked but Ctrl/Cmd+V into an Access key, Secret key, password, or token field did nothing. TuiApplication now routes a paste the same way it routes keys — modal trap first, then the global PasteReceived fallback when no modal is active — via a new Modal.HandlePaste(string) virtual (default no-op) and ModalStack.HandlePaste(string). PromptModal and ListEditorModal<T> override it to insert the pasted text through a new TextField.Insert(string) that places the run at the caret and strips control characters, so a multi-line or newline-terminated clipboard payload (common when copying a key) collapses onto the field's single line instead of submitting or corrupting it. Additive and backward compatible (new virtual defaults to the prior drop-the-paste behavior for any custom modal); 4 new Touchstone cases covering the widget insert, the stack routing, the modal-trap-vs-fallback dispatch, and the end-to-end prompt paste (460 total across console/xUnit/NUnit on net8.0/net10.0). Prior release — v0.8.4 (Alpha) — reusable list editor and hierarchical file selection. Adds ListEditorModal<T>, a single-screen editor for an ordered list of T: items are added through an inline text field that is parsed, validated, and previewed live (with the buffer kept on a rejected commit), the selected item can be removed, and — when enabled — items reorder with Alt+Up/Alt+Down; Enter finishes with a fresh IReadOnlyList<T> and Escape cancels with null (distinct from an empty list). Parsing flows through a named ParseResult<T> (no tuples) and behavior is configured with ListEditorOptions<T> (parser, describe, legend, dedupe, reorder, empty policy, key chords). Adds CheckTree<T>, a forest of expandable nodes with cascading tri-state checkboxes: checking a folder makes its subtree effectively checked, unchecking a descendant carves a hole and marks ancestors partial, effective state is inherited from the nearest explicit ancestor, and the result derives as top-most included roots plus excluded holes; children load lazily and cache once per node, state is keyed through a comparer so regenerated nodes keep their checks, Space toggles and Enter is left for the host. Adds FileSelectModal (with FileSelectOptions, FileSelection, and FileExclusion) wrapping CheckTree<string> over an IFileSystemProvider — the default FileSystemProvider roots at the machine's ready drives and tolerates unreadable directories — with pre-seed-and-reveal of saved includes and holes and a strongly-typed FileSelection result. Fixes two Tree<T> defects that bit any large or generated tree: the children delegate and the disclosure probe are no longer re-invoked per row per render (children are cached; an optional cheap hasChildren probe drives the glyph), and expansion state is now keyed through an optional IEqualityComparer<T> instead of node identity. FileBrowser gains a FileSelectionMode flag (None/Single/Multiple) with SelectedPaths and a Confirmed event for flat multi-select. Additive and backward compatible (Tree<T> and FileBrowser only gain optional parameters and members); 38 new Touchstone cases (456 total across console/xUnit/NUnit on net8.0/net10.0). Prior release — v0.8.3 (Alpha) — uniform Home/End navigation on the remaining small widgets. Completes the page/jump navigation work from v0.8.2 by extending it to the three widgets left out: RadioGroup and the MenuBar drop-down gain Home/End (jump to the first/last option or item), and AutocompleteOverlay gains Home/End plus PageUp/PageDown (by its MaxRows page size). Every navigable list and scroll widget now handles the same keys. Additive and backward compatible; 4 new Touchstone cases (418 total across console/xUnit/NUnit on net8.0/net10.0). Prior release — v0.8.2 (Alpha) — page and jump navigation across list widgets. Adds PageUp/PageDown and Home/End where they were missing so long lists are no longer walked one row at a time. ListView now pages by its viewport height and jumps to the first/last item — which also gives SelectModal (SelectAsync), ActionListView, and ReorderableList the same keys for free. CheckList (and thus MultiSelectModal) gains PageUp/PageDown to complement its existing Home/End; FuzzyList, DataTable, Tree, FileBrowser, and KeyBindingEditor gain all four keys; and ScrollView and DiffView gain Home/End to jump to the top and bottom. Paging steps by the widget's last-rendered viewport height and every jump clamps at the ends. Additive and backward compatible; 16 new Touchstone cases, positive and negative (414 total across console/xUnit/NUnit on net8.0/net10.0). Prior release — v0.8.1 (Alpha) — terminal-restore hardening. Fixes a wedged terminal after a TUIKit app exits via Ctrl+C or an unhandled exception: the shell was left with SGR mouse reporting on (the scroll wheel emitting escape sequences such as ^[[<64;64;15M) and, on Windows, the console still in raw/VT input mode (arrow keys echoing ^[[A). TuiApplication now installs a cross-platform safety net on Start — a Console.CancelKeyPress handler that cancels the runtime's abrupt process kill and routes Ctrl+C into a graceful stop, plus an AppDomain.ProcessExit handler — so the terminal is restored (mouse reporting, bracketed paste, enhanced keyboard, cursor, and the alternate screen) on every exit path. The restore is idempotent and races safely between the run loop and the exit callbacks. ConsoleBackend's process-exit net now also runs on Windows (previously Unix-only) and additionally disables mouse reporting and bracketed paste before restoring the console mode. Additive and backward compatible; 3 new Touchstone cases (398 total across console/xUnit/NUnit on net8.0/net10.0). Prior release — v0.8.0 (Alpha) — text-to-ASCII-art font engine. Adds a reusable, developer-facing ASCII-art component (namespace TUIKit.Ascii). AsciiArt.Render composes text into multi-row art using any IAsciiFont, with a faithful FIGlet layout engine (full-width, kerning, and the six horizontal smushing rules). FigletFontLoader parses .flf and TOIlet .tlf fonts so consumers can bring their own; AsciiFontLibrary is a thread-safe, case-insensitive font manager whose Default instance is pre-populated with 84 built-in fonts (Standard, Slant, Small and its family, Doom, Colossal, Big Money, ANSI Shadow, Sub-Zero, and many more), each a discrete class over an embedded resource. AsciiArtText is a font-aware IWidget (successor to BannerText, which is unchanged). Fonts with restrictive or unclear licensing are excluded from what ships; bundled-font attribution travels in the package. Additive and backward compatible; 30 new Touchstone cases (395 total across console/xUnit/NUnit on net8.0/net10.0). Prior release — v0.6.1 (Alpha) — dependency refresh and test hardening. Updates the netstandard2.0 runtime dependencies (Microsoft.Bcl.AsyncInterfaces and System.Threading.Channels to 10.0.11, System.Memory to 4.6.3) and the test toolchain (Microsoft.NET.Test.Sdk 18.9.0, coverlet.collector 10.0.1, xunit.runner.visualstudio 4.0.0, NUnit 4.6.1 with NUnit.Analyzers 4.14.0 and NUnit3TestAdapter 6.2.0). No public API or behavior changes — this is a maintenance release; the produced package differs only in its declared netstandard2.0 dependency versions. Adds additive negative/positive coverage for FocusManager.SetFocus index guards and focus notification, AxisConstraint factory guards and offset/length resolution, two-key command-sequence conflict handling, per-call StyledConsole render-width validation, TuiApplication.Focus region-id guards, and DialogModal content-height bounds. 365 Touchstone cases across console/xUnit/NUnit on net8.0/net10.0. Prior release — v0.6.0 (Alpha) — horizontal components and per-region backgrounds. Regions gain a background (an explicit color or a named theme role such as sidebar/statusbar) painted behind the border and content. New components: a DialogModal auto-sizing bordered base; CheckList<T> and MultiSelectModal<T>; generic ListView<T> and FuzzyList<T> (BREAKING — string usage becomes ListView<string>/FuzzyList<string>; SelectModal keeps its string+index API); ActionListView<T> (row actions) and ReorderableList<T>; a DefinitionList status panel and ActivityIndicator; a StreamingTranscript pane projector; a Command/CommandRegistry that drives key bindings, menu bar, palette, and slash routing from one list; an AutocompleteOverlay with a pluggable ISuggestionProvider; focus-following ScrollView (IScrollExtent) with runtime Form field rebuild; and utilities HintText, ColumnFormatter, Rule, and SubmitKeyResolver. 363 Touchstone cases across console/xUnit/NUnit on net8.0/net10.0. Prior release — v0.5.1 (Alpha) — documentation fixes; no code changes from v0.5.0. Corrects the README version banner and install snippet to the 0.5 line and archives an internal notes file. Prior release — v0.5.0 (Alpha) — masked text input. TextField gains a MaskChar property (and an IsMasked convenience flag) so a field can obscure its rendered value for secret entry such as passwords, API keys, and bearer tokens. When MaskChar is '\0' (the default) the field renders as before; set it to a visible character (for example '•') and every value character — including the glyph under the caret — draws as the mask while Value and all editing and caret behavior are unchanged. Additive and backward compatible. Prior release — v0.4.1 (Alpha) — input decoding fix. The console input decoder now keeps carriage return (0x0D) and line feed (0x0A) distinct: CR decodes as Enter, while LF decodes as Ctrl+J (Char 'j' + Ctrl). Because no terminal reports Shift+Enter or Ctrl+Enter without the enhanced keyboard protocol (Windows Terminal, macOS Terminal.app, and legacy xterm all send a bare CR), this lets an application bind Ctrl+J as a terminal-independent "insert newline" chord that works everywhere. Bracketed paste is unaffected — pasted newlines are still captured as literal paste text. Prior release — v0.4.0 (Alpha) — the interaction-contract release. The host now assembles the interactive skeleton on the consumer's behalf, turning "read the example and replicate the wiring" into "bind widgets, set focus, run." All additive; the raw escape hatches (KeyReceived, MouseReceived, RenderOverlay) still work. Adds a host-owned focus ring (Focus/FocusNext/FocusPrevious/FocusChanged, FocusContext follows focus, Tab traversal) and the optional IFocusAware focus-state contract; an explicit input-precedence chain (modal → KeyFilter → focus-scoped commands → focused-widget first refusal → focus traversal → global commands → KeyReceived) that fixes global-chord-vs-widget-key collisions; a wired two-key sequence timeout so a dangling prefix no longer swallows the next key; host-owned mouse hit-testing with click-to-focus and wheel/click routing to the optional IMouseAware interface (Pane and ScrollView scroll on the wheel); typed modals via ShowAsync<T> and a Post(Action) loop scheduler for safe off-loop UI mutation; and DockTop/DockBottom/DockLeft/DockRight/Fill application-shell layout helpers. Fixes: Bind now parses the documented "ctrl+k ctrl+t" multi-key syntax instead of throwing; assigning Layout after incremental AddRegion/AddPane/AddWidget is rejected rather than silently discarding regions; version/doc drift corrected. Adds a --contract example demo and a Touchstone Usability suite. API and capabilities are subject to change. Prior release — v0.3.1 (Alpha) — Unix input follow-up. Fixes echoed keystrokes and screen scrolling on macOS/Linux by performing all terminal I/O directly on the standard file descriptors (libc read/write) instead of through System.Console, whose Unix implementation re-cooked the terminal and echoed input behind the termios raw mode. Also decodes F1–F4 in their CSI form (not just SS3), parses Kitty key-release/sub-parameter sequences so a release no longer double-fires commands, and adds a '?' help alias with environment-appropriate key labels. Prior release — v0.3.0 (Alpha) — native cross-platform keyboard input. Fixes Unix raw mode: ConsoleBackend now enters raw mode in-process via the libc termios API (tcgetattr/cfmakeraw/tcsetattr) instead of shelling out to stty (which the .NET runtime silently reverted), so keystrokes, Tab, arrows, function keys, Page Up/Down, and Ctrl-combinations reach the app consistently across Windows, macOS, Linux, and SSH/tmux. Adds KeyChord.ToLabel + KeyLabelStyle + KeyLabel.Recommended for environment-appropriate key hints (Ctrl+G on Windows/Linux, ⌃G on macOS), a process-exit safety net that restores the terminal on abnormal exit, and expanded input-decoder tests. API and capabilities are subject to change. Prior release — v0.2.0 (Alpha) — hardening and documentation release. Adds a comprehensive battery of validation tests (256 Touchstone cases across the console, xUnit, and NUnit runners on net8.0/net10.0) asserting every documented argument guard and range bound, plus a refreshed README, an expanded Building Terminal Apps guide, and full v0.2.0 packaging metadata. The feature surface is unchanged from v0.1.0: concurrency-first core (region layout, thread-safe panes, double-buffered diff rendering, Unicode width, enhanced input, mouse/OSC 8 links, modals and notifications, theming, headless backend) plus the full widget toolkit (tables, trees, tabs, forms, menus, fuzzy finder, charts, gauges/progress, color picker, file browser, diff viewer, syntax highlighting, banners, half-block/sixel/kitty images), reactive binding, animation, and a mouse-capture toggle for native text selection. API and capabilities are subject to change.