FractalDataWorks.UI.Components.TUI 0.1.0-preview.11

This is a prerelease version of FractalDataWorks.UI.Components.TUI.
dotnet add package FractalDataWorks.UI.Components.TUI --version 0.1.0-preview.11
                    
NuGet\Install-Package FractalDataWorks.UI.Components.TUI -Version 0.1.0-preview.11
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="FractalDataWorks.UI.Components.TUI" Version="0.1.0-preview.11" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FractalDataWorks.UI.Components.TUI" Version="0.1.0-preview.11" />
                    
Directory.Packages.props
<PackageReference Include="FractalDataWorks.UI.Components.TUI" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add FractalDataWorks.UI.Components.TUI --version 0.1.0-preview.11
                    
#r "nuget: FractalDataWorks.UI.Components.TUI, 0.1.0-preview.11"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package FractalDataWorks.UI.Components.TUI@0.1.0-preview.11
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=FractalDataWorks.UI.Components.TUI&version=0.1.0-preview.11&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=FractalDataWorks.UI.Components.TUI&version=0.1.0-preview.11&prerelease
                    
Install as a Cake Tool

FractalDataWorks.UI.Components.TUI

Terminal User Interface (TUI) implementation of the FractalDataWorks UI component system using Spectre.Console.

Overview

This package implements a Terminal UI layer for FractalDataWorks components. It inherits from ComponentBase<TSelf, TModel> and renders to the console using Spectre.Console's rich rendering capabilities including prompts, selections, tables, trees, and progress indicators.

Key Features:

  • Spectre.Console integration with colors, styles, and layouts
  • Interactive prompts for all data types
  • Single and multi-select for TypeCollections and collections
  • Table and tree view rendering
  • Theming via TypeCollections (IMenuTheme, IColorPalette, IBorderStyle)

Target Framework: net10.0

Dependencies:

  • FractalDataWorks.UI.Abstractions
  • FractalDataWorks.UI.Themes
  • FractalDataWorks.Collections
  • Spectre.Console

Architecture

┌─────────────────────────────────────────────┐
│ FractalDataWorks.UI.Abstractions            │
│ - ComponentBase<TSelf, TModel>              │
│ - PropertyComponent<TSelf, TProperty>       │
│ - CollectionComponent                       │
│ - ZERO UI framework dependencies            │
└─────────────────────────────────────────────┘
                    ▲
                    │ inherits
┌─────────────────────────────────────────────┐
│ FractalDataWorks.UI.Components.TUI         │  ← THIS PACKAGE
│ - TUIComponent<TSelf, TModel>               │
│ - Spectre.Console rendering                 │
│ - Interactive prompts                       │
│ - Table/Tree displays                       │
│ - TypeCollection selectors                  │
└─────────────────────────────────────────────┘

Core Components

TUIComponent<TSelf, TModel>

Foundation for all terminal UI components. Inherits from ComponentBase and adds Spectre.Console rendering.

From TUIComponent.cs:16-72:

/// <summary>
/// CRTP base for Terminal UI components using Spectre.Console.
/// Provides interactive prompting and rich console rendering.
/// </summary>
/// <typeparam name="TSelf">The derived TUI component type (CRTP)</typeparam>
/// <typeparam name="TModel">The model type being rendered</typeparam>
public abstract class TUIComponent<TSelf, TModel> : ComponentBase<TSelf, TModel>
    where TSelf : TUIComponent<TSelf, TModel>
{
    /// <summary>
    /// Theme configuration for this component.
    /// </summary>
    public TUIThemeConfiguration? Theme { get; set; }

    /// <summary>
    /// Prompts the user to enter/edit the model value interactively.
    /// </summary>
    /// <param name="console">The console to prompt on</param>
    /// <returns>The entered/edited model value</returns>
    public abstract Task<TModel?> Prompt(IAnsiConsole console);

    /// <summary>
    /// Renders the current model value to the console (read-only display).
    /// </summary>
    /// <param name="console">The console to render to</param>
    public abstract void Render(IAnsiConsole console);

    /// <summary>
    /// Gets display text for this component (for use in lists/tables).
    /// </summary>
    public virtual string GetDisplayText()
    {
        return Value?.ToString() ?? "[dim]null[/]";
    }

    /// <summary>
    /// Renders a section header with the component name.
    /// </summary>
    protected virtual void RenderHeader(IAnsiConsole console, string? title = null)
    {
        var headerTitle = title ?? typeof(TModel).Name;
        var color = Theme?.Colors.Primary ?? Color.Yellow;

        console.Write(new Rule($"[{color}]{headerTitle}[/]").LeftJustified());
    }

    /// <summary>
    /// Prompts for a TypeCollection selection.
    /// </summary>
    protected virtual int PromptTypeCollectionId<TCollection, TOption>(
        IAnsiConsole console,
        string promptText,
        int currentId = 0)
        where TCollection : class
        where TOption : class, ITypeOption
    {
        return Prompts.TypeCollectionPromptHelper.Prompt<TCollection, TOption>(
            console,
            promptText,
            currentId,
            Theme);
    }
}

TUI-Specific Features:

  • Prompt() - Interactive data entry (abstract, implemented by derived classes)
  • Render() - Display-only rendering (abstract, implemented by derived classes)
  • RenderHeader() - Renders a styled section header
  • PromptTypeCollectionId() - Helper for TypeCollection selection

TUIPropertyComponent<TSelf, TProperty>

Base for property-level TUI components with type-specific prompts.

From TUIPropertyComponent.cs:12-48:

/// <summary>
/// CRTP base for Terminal UI property-level components.
/// </summary>
/// <typeparam name="TSelf">The derived property component type (CRTP)</typeparam>
/// <typeparam name="TProperty">The property value type</typeparam>
public abstract class TUIPropertyComponent<TSelf, TProperty> : PropertyComponent<TSelf, TProperty>
    where TSelf : TUIPropertyComponent<TSelf, TProperty>
{
    /// <summary>
    /// Theme configuration.
    /// </summary>
    public TUIThemeConfiguration? Theme { get; set; }

    /// <summary>
    /// Prompts the user to enter a value for this property.
    /// </summary>
    public abstract TProperty? PromptValue(IAnsiConsole console);

    /// <summary>
    /// Renders this property value to the console.
    /// </summary>
    public abstract void RenderValue(IAnsiConsole console);

    /// <summary>
    /// Gets the prompt text for this property.
    /// </summary>
    protected virtual string GetPromptText()
    {
        return Metadata?.Label ?? typeof(TProperty).Name;
    }

    /// <summary>
    /// Displays help text if available.
    /// </summary>
    protected virtual void RenderHelpText(IAnsiConsole console)
    {
        if (!string.IsNullOrEmpty(Metadata?.HelpText))
        {
            console.MarkupLine($"[dim]{Metadata.HelpText}[/]");
        }
    }
}

Prompt Helpers

TextPromptHelper

Helper for creating text prompts with validation.

From Prompts/TextPromptHelper.cs:21-72:

public static string Prompt(
    IAnsiConsole console,
    string promptText,
    string? defaultValue = null,
    PropertyMetadata? metadata = null,
    TUIThemeConfiguration? theme = null)
{
    var prompt = new TextPrompt<string>(promptText);

    if (!string.IsNullOrEmpty(defaultValue))
    {
        prompt.DefaultValue(defaultValue);
    }

    if (metadata?.Required == true)
    {
        prompt.AllowEmpty = false;
        prompt.ValidationErrorMessage($"[{theme?.Colors.Error ?? Color.Red}]{promptText} is required[/]");
    }

    if (metadata?.ValidationRules.TryGetValue("maxLength", out var maxLengthObj) == true
        && maxLengthObj is int maxLength)
    {
        prompt.Validate(value =>
        {
            if (value.Length > maxLength)
            {
                return ValidationResult.Error($"[{theme?.Colors.Error ?? Color.Red}]Maximum length is {maxLength}[/]");
            }
            return ValidationResult.Success();
        });
    }

    return console.Prompt(prompt);
}

NumericPromptHelper

Helper for creating numeric prompts with range validation.

From Prompts/NumericPromptHelper.cs:22-65:

public static T Prompt<T>(
    IAnsiConsole console,
    string promptText,
    T? defaultValue = default,
    PropertyMetadata? metadata = null,
    TUIThemeConfiguration? theme = null)
    where T : struct, IComparable<T>
{
    var prompt = new TextPrompt<T>(promptText);

    if (defaultValue != null && !defaultValue.Equals(default(T)))
    {
        prompt.DefaultValue(defaultValue.Value);
    }

    // Range validation
    if (metadata?.ValidationRules.TryGetValue("min", out var minObj) == true)
    {
        var min = (T)Convert.ChangeType(minObj, typeof(T), CultureInfo.InvariantCulture);
        prompt.Validate(value =>
        {
            if (value.CompareTo(min) < 0)
            {
                return ValidationResult.Error($"[{theme?.Colors.Error ?? Color.Red}]Value must be at least {min}[/]");
            }
            return ValidationResult.Success();
        });
    }

    return console.Prompt(prompt);
}

ConfirmationPromptHelper

Helper for creating Yes/No confirmation prompts.

From Prompts/ConfirmationPromptHelper.cs:17-25:

public static bool Prompt(
    IAnsiConsole console,
    string promptText,
    bool defaultValue = false,
    TUIThemeConfiguration? theme = null)
{
    return console.Confirm(promptText, defaultValue);
}

TypeCollectionPromptHelper

Helper for prompting TypeCollection selections.

From Prompts/TypeCollectionPromptHelper.cs:24-59:

public static int Prompt<TCollection, TOption>(
    IAnsiConsole console,
    string promptText,
    int currentId = 0,
    TUIThemeConfiguration? theme = null)
    where TCollection : class
    where TOption : class, ITypeOption
{
    // Get TypeCollection instance via reflection
    var instanceProperty = typeof(TCollection).GetProperty("Instance",
        BindingFlags.Public | BindingFlags.Static);
    var instance = instanceProperty?.GetValue(null);

    if (instance == null)
    {
        throw new InvalidOperationException($"Cannot access {typeof(TCollection).Name}.Instance");
    }

    // Get All() method
    var allMethod = typeof(TCollection).GetMethod("All");
    var options = (allMethod?.Invoke(instance, null) as IEnumerable<TOption>)?.ToList();

    if (options == null || options.Count == 0)
    {
        console.MarkupLine($"[{theme?.Colors.Warning ?? Color.Yellow}]No options available[/]");
        return 0;
    }

    var selection = console.Prompt(
        new SelectionPrompt<TOption>()
            .Title(promptText)
            .AddChoices(options)
            .UseConverter(opt => $"{opt.Name} [dim]({opt.Id})[/]"));

    return (int)selection.Id;
}

CollectionPromptHelper

Helper for interactive collection management with add/view/edit/remove.

From Prompts/CollectionPromptHelper.cs:27-79:

public static async Task<List<T>> PromptCollection<T, TComponent>(
    IAnsiConsole console,
    List<T>? items,
    Func<T, int, TComponent> createComponent,
    Func<T> createNewItem,
    TUIThemeConfiguration? theme = null)
    where TComponent : TUIComponent<TComponent, T>
{
    items ??= [];

    while (true)
    {
        console.Clear();
        RenderCollectionTable(console, items, createComponent, theme);

        List<string> choices = [];
        if (items.Count > 0)
        {
            choices.Add("View Item");
            choices.Add("Edit Item");
            choices.Add("Remove Item");
        }
        choices.Add("Add Item");
        choices.Add("Done");

        var action = console.Prompt(
            new SelectionPrompt<string>()
                .Title("[bold]Collection Actions:[/]")
                .AddChoices(choices));

        switch (action)
        {
            case "View Item":
                await ViewItem(console, items, createComponent).ConfigureAwait(false);
                break;
            case "Edit Item":
                await EditItem(console, items, createComponent).ConfigureAwait(false);
                break;
            case "Remove Item":
                RemoveItem(console, items);
                break;
            case "Add Item":
                await AddItem(console, items, createNewItem, createComponent).ConfigureAwait(false);
                break;
            case "Done":
                return items;
        }

        console.WriteLine();
        console.Write("Press any key to continue...");
        Console.ReadKey(true);
    }
}

Renderers

TableRenderer

Renders collections as formatted tables.

From Renderers/TableRenderer.cs:22-53:

public static void RenderTable<T>(
    IAnsiConsole console,
    IEnumerable<T> items,
    Dictionary<string, Func<T, string>> columns,
    TUIThemeConfiguration? theme = null)
{
    var border = theme?.Borders.Table ?? TableBorder.Rounded;
    var borderColor = theme?.Colors.Primary ?? Color.Blue;

    var table = new Table()
        .Border(border)
        .BorderColor(borderColor);

    // Add columns
    foreach (var columnName in columns.Keys)
    {
        table.AddColumn($"[bold]{columnName}[/]");
    }

    // Add rows
    foreach (var item in items)
    {
        List<string> cellValues = [];
        foreach (var columnFunc in columns.Values)
        {
            cellValues.Add(columnFunc(item));
        }
        table.AddRow(cellValues.ToArray());
    }

    console.Write(table);
}

TreeRenderer

Renders hierarchical data as trees.

From Renderers/TreeRenderer.cs:18-27:

public static void RenderTree(
    IAnsiConsole console,
    string rootLabel,
    Action<IHasTreeNodes> buildTree,
    TUIThemeConfiguration? theme = null)
{
    var tree = new Tree(rootLabel);
    buildTree(tree);
    console.Write(tree);
}

PanelRenderer

Renders content in bordered panels.

From Renderers/PanelRenderer.cs:17-36:

public static void RenderPanel(
    IAnsiConsole console,
    string content,
    string? title = null,
    TUIThemeConfiguration? theme = null)
{
    var border = theme?.Borders.Panel ?? BoxBorder.Rounded;
    var borderColor = theme?.Colors.Primary ?? Color.Blue;

    var panel = new Panel(new Markup(content))
        .Border(border)
        .BorderColor(borderColor);

    if (!string.IsNullOrEmpty(title))
    {
        panel.Header(title);
    }

    console.Write(panel);
}

Theme System

Theme configuration wraps IMenuTheme from the UI.Themes package, providing access to colors, borders, and icons as TypeCollections.

From Theme/TUIThemeConfiguration.cs:12-80:

public class TUIThemeConfiguration
{
    private IMenuTheme _theme;

    /// <summary>
    /// Initializes a new instance with the default dark theme.
    /// </summary>
    public TUIThemeConfiguration()
    {
        _theme = MenuThemes.ByName("Dark");
    }

    /// <summary>
    /// Initializes a new instance with the specified theme.
    /// </summary>
    public TUIThemeConfiguration(IMenuTheme theme)
    {
        _theme = theme;
    }

    /// <summary>
    /// Gets or sets the theme by Id.
    /// </summary>
    public int ThemeId
    {
        get => _theme.Id;
        set => _theme = MenuThemes.ById(value);
    }

    /// <summary>
    /// Gets or sets the theme by name.
    /// </summary>
    public string ThemeName
    {
        get => _theme.Name;
        set => _theme = MenuThemes.ByName(value);
    }

    /// <summary>
    /// Gets the current theme.
    /// </summary>
    public IMenuTheme Theme => _theme;

    /// <summary>
    /// Gets the color palette from the current theme.
    /// </summary>
    public IColorPalette Colors => _theme.Colors;

    /// <summary>
    /// Gets the border style from the current theme.
    /// </summary>
    public IBorderStyle Borders => _theme.Borders;

    /// <summary>
    /// Gets the icon set from the current theme.
    /// </summary>
    public IIconSet Icons => _theme.Icons;

    /// <summary>
    /// Gets or sets whether to use color in output.
    /// </summary>
    public bool UseColor { get; set; } = true;

    /// <summary>
    /// Gets or sets whether to use emoji/icons in output.
    /// </summary>
    public bool UseEmoji { get; set; } = true;
}

Theme Properties:

The theme provides access to:

  • Colors - IColorPalette with Primary, Secondary, Success, Warning, Error, Info, etc.
  • Borders - IBorderStyle with Panel, Table, Input, Menu, Dialog borders
  • Icons - IIconSet for UI icons

Themes are TypeCollections from FractalDataWorks.UI.Themes and can be selected by name or ID.

Usage

Using Prompt Helpers

using FractalDataWorks.UI.Components.TUI;
using FractalDataWorks.UI.Components.TUI.Prompts;
using Spectre.Console;

var console = AnsiConsole.Console;
var theme = new TUIThemeConfiguration(); // Uses default "Dark" theme

// Text prompt with validation
var name = TextPromptHelper.Prompt(
    console,
    "Enter name:",
    defaultValue: "Default",
    metadata: new PropertyMetadata { Required = true });

// Numeric prompt
var port = NumericPromptHelper.Prompt<int>(
    console,
    "Port:",
    defaultValue: 8080);

// Confirmation
var confirm = ConfirmationPromptHelper.Prompt(
    console,
    "Continue?",
    defaultValue: true);

Using Renderers

using FractalDataWorks.UI.Components.TUI.Renderers;
using Spectre.Console;

var console = AnsiConsole.Console;
var theme = new TUIThemeConfiguration();

// Render a table
var items = new[] { ("Item1", 10), ("Item2", 20) };
TableRenderer.RenderTable(
    console,
    items,
    new Dictionary<string, Func<(string, int), string>>
    {
        ["Name"] = x => x.Item1,
        ["Value"] = x => x.Item2.ToString()
    },
    theme);

// Render a tree
TreeRenderer.RenderTree(
    console,
    "[yellow]Configuration[/]",
    tree =>
    {
        var node = tree.AddNode("Settings");
        node.AddNode("Option 1");
        node.AddNode("Option 2");
    },
    theme);

// Render a panel
PanelRenderer.RenderPanel(
    console,
    "[bold]Name:[/] Example\n[bold]Status:[/] Active",
    title: "Details",
    theme);

Interactive Collection Management

using FractalDataWorks.UI.Components.TUI.Prompts;
using Spectre.Console;

var console = AnsiConsole.Console;

// Manage a collection interactively
var items = await CollectionPromptHelper.PromptCollection<MyItem, MyItemComponent>(
    console,
    items: existingItems,
    createComponent: (item, index) => new MyItemComponent { Value = item },
    createNewItem: () => new MyItem());

Best Practices

  1. Escape user input - Use Markup.Escape() to prevent markup injection
  2. Provide defaults - All prompts should have sensible default values
  3. Use theme configuration - Pass TUIThemeConfiguration to helpers for consistent styling
  4. Validate immediately - Use .Validate() on prompts for real-time feedback
  5. Handle cancellation - Catch OperationCanceledException for Ctrl+C

See Also

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on FractalDataWorks.UI.Components.TUI:

Package Downloads
FractalDataWorks.UI.Components.RazorConsole

Development tools and utilities for the FractalDataWorks ecosystem. Build:

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0-preview.11 36 1/12/2026
0.1.0-preview.10 35 1/12/2026
0.1.0-preview.9 44 1/9/2026
0.1.0-preview.7 100 1/7/2026