Termina 0.5.0
See the version list below for details.
dotnet add package Termina --version 0.5.0
NuGet\Install-Package Termina -Version 0.5.0
<PackageReference Include="Termina" Version="0.5.0" />
<PackageVersion Include="Termina" Version="0.5.0" />
<PackageReference Include="Termina" />
paket add Termina --version 0.5.0
#r "nuget: Termina, 0.5.0"
#:package Termina@0.5.0
#addin nuget:?package=Termina&version=0.5.0
#tool nuget:?package=Termina&version=0.5.0
Termina
![]()
Termina is a reactive terminal UI (TUI) framework for .NET with declarative layouts and surgical region-based rendering. It provides an MVVM architecture with source-generated reactive properties, ASP.NET Core-style routing, and seamless integration with Microsoft.Extensions.Hosting.
Documentation
Features
- Reactive MVVM Architecture - ViewModels with
[Reactive]attribute for source-generated observable properties - Declarative Layouts - Tree-based layout system with size constraints (Fixed, Fill, Auto, Percent)
- Surgical Rendering - Only changed regions re-render, enabling smooth streaming updates
- ASP.NET Core-Style Routing - Route templates with parameters (
/tasks/{id:int}) and type constraints - Source Generators - AOT-compatible code generation for reactive properties and route parameters
- Streaming Support - Native
StreamingTextNodefor real-time content like LLM output - Dependency Injection - Full integration with
Microsoft.Extensions.DependencyInjection - Hosting Integration - Works with
Microsoft.Extensions.Hostingfor clean lifecycle management
Installation
dotnet add package Termina
dotnet add package Microsoft.Extensions.Hosting
Quick Start
1. Define a ViewModel
using System.Reactive.Linq;
using Termina.Input;
using Termina.Reactive;
public partial class CounterViewModel : ReactiveViewModel
{
[Reactive] private int _count;
[Reactive] private string _message = "Press Up/Down to change count";
public override void OnActivated()
{
Input.OfType<KeyPressed>()
.Subscribe(HandleKey)
.DisposeWith(Subscriptions);
}
private void HandleKey(KeyPressed key)
{
switch (key.KeyInfo.Key)
{
case ConsoleKey.UpArrow:
Count++;
Message = $"Count: {Count}";
break;
case ConsoleKey.DownArrow:
Count--;
Message = $"Count: {Count}";
break;
case ConsoleKey.Escape:
Shutdown();
break;
}
}
}
The [Reactive] attribute generates:
- A
BehaviorSubject<T>backing field - A public property
Countwith get/set - An
IObservable<T>propertyCountChangedfor subscriptions
2. Define a Page
using System.Reactive.Linq;
using Termina.Extensions;
using Termina.Layout;
using Termina.Reactive;
using Termina.Rendering;
using Termina.Terminal;
public class CounterPage : ReactivePage<CounterViewModel>
{
public override ILayoutNode BuildLayout()
{
return Layouts.Vertical()
.WithChild(
new PanelNode()
.WithTitle("Counter Demo")
.WithBorder(BorderStyle.Rounded)
.WithBorderColor(Color.Cyan)
.WithContent(
ViewModel.CountChanged
.Select(count => new TextNode($"Count: {count}")
.WithForeground(Color.BrightCyan))
.AsLayout())
.Height(5))
.WithChild(
ViewModel.MessageChanged
.Select(msg => new TextNode(msg))
.AsLayout()
.Height(1));
}
}
3. Configure and Run
using Microsoft.Extensions.Hosting;
using Termina.Hosting;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddTermina("/counter", termina =>
{
termina.RegisterRoute<CounterPage, CounterViewModel>("/counter");
});
await builder.Build().RunAsync();
Layout System
Termina uses a declarative tree-based layout system:
Layouts.Vertical()
.WithChild(header.Height(3)) // Fixed height
.WithChild(content.Fill()) // Take remaining space
.WithChild(sidebar.Width(20)) // Fixed width
.WithChild(footer.Height(1)); // Fixed height
Layouts.Horizontal()
.WithChild(menu.Width(30))
.WithChild(main.Fill(2)) // 2x weight
.WithChild(aside.Fill(1)); // 1x weight
Routing
ASP.NET Core-style route templates with parameter support:
builder.Services.AddTermina("/", termina =>
{
termina.RegisterRoute<HomePage, HomeViewModel>("/");
termina.RegisterRoute<TasksPage, TasksViewModel>("/tasks");
termina.RegisterRoute<TaskDetailPage, TaskDetailViewModel>("/tasks/{id:int}");
termina.RegisterRoute<UserPage, UserViewModel>("/users/{name}");
});
Route Parameter Injection
public partial class TaskDetailViewModel : ReactiveViewModel
{
[FromRoute] private int _id; // Injected from route
public override void OnActivated()
{
LoadTask(Id); // Id is already populated
}
}
Navigation
Navigate("/tasks/42");
NavigateWithParams("/tasks/{id}", new { id = 42 });
Shutdown(); // Exit the application
Streaming Content
For real-time content like LLM output, Pages own StreamingTextNode and subscribe to ViewModel observables:
// In Page
private StreamingTextNode _output = null!;
protected override void OnBound()
{
_output = StreamingTextNode.Create();
ViewModel.StreamOutput.Subscribe(chunk => _output.Append(chunk));
}
// In ViewModel
public IObservable<string> StreamOutput => _streamOutput.AsObservable();
private readonly Subject<string> _streamOutput = new();
private async Task StreamResponse()
{
await foreach (var chunk in GetStreamingData())
{
_streamOutput.OnNext(chunk); // Character-level updates
}
}
Testing
VirtualInputSource enables automated testing:
var scriptedInput = new VirtualInputSource();
builder.Services.AddTerminaVirtualInput(scriptedInput);
scriptedInput.EnqueueKey(ConsoleKey.UpArrow);
scriptedInput.EnqueueString("Hello World");
scriptedInput.EnqueueKey(ConsoleKey.Enter);
scriptedInput.Complete();
await host.RunAsync();
Requirements
- .NET 10.0 or later
- AOT-compatible (Native AOT publishing supported)
License
Apache 2.0 - See LICENSE for details.
Contributing
Contributions are welcome! See CONTRIBUTING.md for development setup and guidelines.
| 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
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 9.0.0)
- System.Reactive (>= 6.1.0)
- Termina.Generators (>= 0.5.0)
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.10.2 | 1,802 | 5/30/2026 |
| 0.10.1 | 1,340 | 5/24/2026 |
| 0.10.0 | 389 | 5/23/2026 |
| 0.9.0 | 2,386 | 5/18/2026 |
| 0.8.0 | 7,014 | 3/17/2026 |
| 0.7.2 | 1,954 | 3/1/2026 |
| 0.7.1 | 412 | 2/27/2026 |
| 0.7.0 | 111 | 2/26/2026 |
| 0.6.1 | 107 | 2/25/2026 |
| 0.6.0 | 306 | 2/24/2026 |
| 0.5.1 | 488 | 12/19/2025 |
| 0.5.0 | 320 | 12/18/2025 |
| 0.4.0 | 361 | 12/18/2025 |
| 0.3.0 | 297 | 12/17/2025 |
| 0.2.1 | 759 | 12/16/2025 |
| 0.2.0 | 306 | 12/16/2025 |
| 0.1.0 | 324 | 12/16/2025 |
| 0.1.0-beta1 | 140 | 12/12/2025 |
**New Features**:
- **BlockSegment wrapper for block-level streaming text** ([#108](https://github.com/Aaronontheweb/Termina/pull/108))
- Enables text segments to render as block elements starting on new lines with vertical word wrapping
- New `BlockSegment` class wraps any `ITextSegment` for block-level rendering
- Fluent `.AsBlock()` API for creating block segments
- `StreamingTextNode` automatically detects `BlockSegment` and inserts newlines
- Supports both static and animated content (spinners, status indicators)
- Perfect for LLM agent "thinking" displays and streaming content that should update in-place within a fixed area
- Closes [#107](https://github.com/Aaronontheweb/Termina/issues/107)
- **Page-level key binding system with capture phase** ([#105](https://github.com/Aaronontheweb/Termina/pull/105))
- Pages can now intercept keyboard input before focused components receive it
- New `PageKeyBindings` class for registering page-level key handlers
- Added `HandlePageInput` method to `IBindablePage` interface
- `TerminaApplication.ProcessEvent()` now uses capture phase (page) then bubble phase (focused component)
- `ReactivePage` exposes `KeyBindings` property for navigation key handling
- Enables reliable Escape, Tab, and other navigation keys in complex pages
- Closes [#104](https://github.com/Aaronontheweb/Termina/issues/104)
- **GridNode 2D layout primitive** ([#100](https://github.com/Aaronontheweb/Termina/pull/100))
- New 2D grid layout enabling consistent column/row sizing across all cells
- Cell addressing via `SetCell(row, col, node)` or fluent `AddRow()` API
- Constraint-based sizing for columns and rows (Fixed, Auto, Fill, Percent)
- Grid line rendering with Single, Double, Rounded, and Ascii border styles
- Cell spanning support (`colspan` and `rowspan`) for merged header cells
- Focus navigation with 2D arrow key support
- Navigation modes: None, CellNavigation, ChildFocusRouting
- Perfect for dashboards, data tables, and structured layouts
- **TextNode horizontal alignment** ([#100](https://github.com/Aaronontheweb/Termina/pull/100))
- New `TextAlignment` enum: Left (default), Center, Right
- Fluent methods: `Align()`, `AlignCenter()`, `AlignRight()`
- Works seamlessly with GridNode cells and other layout containers
- **Component gallery demo** ([#103](https://github.com/Aaronontheweb/Termina/pull/103))
- New `Termina.Demo.Gallery` project showcasing UI components
- Interactive gallery pages for SelectionList, Grid, and other components
- Run with: `dotnet run --project demos/Termina.Demo.Gallery`
**Bug Fixes**:
- **Fix SelectionListNode number prefixes** ([#103](https://github.com/Aaronontheweb/Termina/pull/103))
- SelectionListNode now shows number prefixes for all items, not just items 1-9
- Fixes [#101](https://github.com/Aaronontheweb/Termina/issues/101)
- **Fix SelectionListNode "Other" option auto-start** ([#103](https://github.com/Aaronontheweb/Termina/pull/103))
- Navigating to "Other" option with arrow keys no longer auto-starts text input
- Text input mode now requires explicit Enter or Space key press
- Fixes [#102](https://github.com/Aaronontheweb/Termina/issues/102)
- **Fix async test method not awaiting** ([#109](https://github.com/Aaronontheweb/Termina/pull/109))
- Corrected test method signature to properly await async operations
- Improves test reliability and prevents potential race conditions
---