Skip to content

Getting Started

Hex1b is a .NET library for building terminal user interfaces (TUI). If you are familiiar with the way that React is used in the browser to construct a virtual DOM which is then synchronized with the real browser DOM then this should feel familiar.

Hex1b supports two styles of terminal UI:

  • Full-screen apps use the alternate screen buffer — like vim or htop. The terminal is restored when the app exits. This guide covers full-screen apps.
  • Flow apps render interactive steps inline in the normal terminal buffer — like npm init or dotnet new. Each step's output stays in scrollback. See Your First Flow App if this is what you're building.

Prerequisites

  • .NET 10 or later
  • A terminal that supports ANSI escape sequences (most modern terminals do)

Create a New Console Application

First, create a new console application:

dotnet new console -n MyTodoApp && cd MyTodoApp

Install Hex1b

Add the Hex1b package to your project:

dotnet add package Hex1b --version {{version}}

Step 1: Hello World

Let's start with a simple "Hello World" example. Replace the contents of Program.cs:

csharp
using Hex1b;

var app = new Hex1bApp(ctx => ctx.Text("Hello, Hex1b!"));
await app.RunAsync();

Press Ctrl+C to exit.

The fluent API uses the ctx (context) parameter to create widgets. In this case, ctx.Text() creates a text widget.

Step 2: Adding State and Interactivity

Now let's add a button and some state to track clicks:

csharp
using Hex1b;

var state = new CounterState();

var app = new Hex1bApp(ctx =>
    ctx.Border(b => [
        b.Text($"Button pressed {state.Count} times"),
        b.Text(""),
        b.Button("Click me!").OnClick(_ => state.Count++)
    ], title: "Counter Demo")
);

await app.RunAsync();

// Define a simple state class to hold our counter
class CounterState
{
    public int Count { get; set; }
}

Key concepts:

  • State: We created a CounterState class to hold mutable data
  • Border: The ctx.Border() creates a widget with a border and optional title
  • Collection expressions: [...] syntax creates an array of child widgets
  • Button: The ctx.Button() creates an interactive button; use .OnClick() for the click handler

Step 3: Building a Todo List

Let's build a simple todo list that displays items and lets you toggle them:

csharp
using Hex1b;

var state = new TodoState();

var app = new Hex1bApp(ctx =>
    ctx.VStack(v => [
        v.Border(b => [
            b.Text("📋 My Todos"),
            b.Text(""),
            b.List(state.FormatItems()).OnItemActivated(e => state.ToggleItem(e.ActivatedIndex))
        ], title: "Todo List").Fill(),
        v.InfoBar("↑↓ Navigate  Space: Toggle")
    ])
);

await app.RunAsync();

class TodoState
{
    public List<(string Text, bool Done)> Items { get; } = 
    [
        ("Learn Hex1b", true),
        ("Build a TUI", false),
        ("Deploy to production", false)
    ];

    public IReadOnlyList<string> FormatItems() =>
        Items.Select(i => $"[{(i.Done ? "✓" : " ")}] {i.Text}").ToList();

    public void ToggleItem(int index)
    {
        var item = Items[index];
        Items[index] = (item.Text, !item.Done);
    }
}

New concepts:

  • VStack: ctx.VStack() arranges children vertically
  • List widget: ctx.List() creates a scrollable, selectable list; use .OnItemActivated() for activation events
  • Fill: .Fill() makes a widget expand to fill available space
  • InfoBar: ctx.InfoBar() displays help text pinned to the bottom of the screen
  • Navigation: Use arrow keys to navigate, Space or Enter to toggle items

Step 4: Adding New Items

Let's add the ability to create new todo items with a text input:

csharp
using Hex1b;
using Hex1b.Widgets;

var state = new TodoState();

var app = new Hex1bApp(ctx =>
    ctx.VStack(v => [
        v.Border(b => [
            b.HStack(h => [
                h.Text("New task: "),
                h.TextBox(state.NewItemText).OnTextChanged(e => state.NewItemText = e.NewText),
                h.Button("Add").OnClick(_ => state.AddItem())
            ]),
            new SeparatorWidget(),
            b.List(state.FormatItems()).OnItemActivated(e => state.ToggleItem(e.ActivatedIndex))
        ], title: "📋 Todo").Fill(),
        v.InfoBar("Tab: Focus next  Space: Toggle")
    ])
);

await app.RunAsync();

class TodoState
{
    public List<(string Text, bool Done)> Items { get; } = 
    [
        ("Learn Hex1b", true),
        ("Build a TUI", false)
    ];

    public string NewItemText { get; set; } = "";

    public IReadOnlyList<string> FormatItems() =>
        Items.Select(i => $"[{(i.Done ? "✓" : " ")}] {i.Text}").ToList();

    public void AddItem()
    {
        if (!string.IsNullOrWhiteSpace(NewItemText))
        {
            Items.Add((NewItemText, false));
            NewItemText = "";
        }
    }

    public void ToggleItem(int index)
    {
        var item = Items[index];
        Items[index] = (item.Text, !item.Done);
    }
}

New concepts:

  • HStack: ctx.HStack() arranges children horizontally
  • TextBox: ctx.TextBox() creates an editable text input; use .OnTextChanged() to handle changes
  • Separator: new SeparatorWidget() creates a horizontal line divider
  • Focus: Use Tab to move focus between interactive widgets

Step 5: Complete Todo Application

Let's add a few more features to complete our todo app:

csharp
using Hex1b;
using Hex1b.Widgets;

var state = new TodoState();

var app = new Hex1bApp(ctx =>
    ctx.VStack(v => [
        v.Border(b => [
            b.Text($"📋 Todo List ({state.RemainingCount} remaining)"),
            b.Text(""),
            b.HStack(h => [
                h.Text("New: "),
                h.TextBox(state.NewItemText).OnTextChanged(e => state.NewItemText = e.NewText),
                h.Button("Add").OnClick(_ => state.AddItem())
            ]),
            new SeparatorWidget(),
            b.List(state.FormatItems())
                .OnSelectionChanged(e => state.SelectedIndex = e.SelectedIndex)
                .OnItemActivated(e => state.ToggleItem(e.ActivatedIndex)),
            b.Text(""),
            b.Button("Delete Selected").OnClick(_ => state.DeleteItem(state.SelectedIndex))
        ], title: "My Todos").Fill(),
        v.InfoBar("↑↓: Navigate  Space: Toggle  Tab: Focus  Del: Delete")
    ])
);

await app.RunAsync();

class TodoState
{
    public List<(string Text, bool Done)> Items { get; } = 
    [
        ("Learn Hex1b", true),
        ("Build a TUI", false),
        ("Deploy to production", false)
    ];

    public string NewItemText { get; set; } = "";
    public int SelectedIndex { get; set; }

    public int RemainingCount => Items.Count(i => !i.Done);

    public IReadOnlyList<string> FormatItems() =>
        Items.Select(i => $"[{(i.Done ? "✓" : " ")}] {i.Text}").ToList();

    public void AddItem()
    {
        if (!string.IsNullOrWhiteSpace(NewItemText))
        {
            Items.Add((NewItemText, false));
            NewItemText = "";
        }
    }

    public void ToggleItem(int index)
    {
        var item = Items[index];
        Items[index] = (item.Text, !item.Done);
    }

    public void DeleteItem(int index)
    {
        if (index >= 0 && index < Items.Count)
        {
            Items.RemoveAt(index);
            if (SelectedIndex >= Items.Count && Items.Count > 0)
            {
                SelectedIndex = Items.Count - 1;
            }
        }
    }
}

Final features:

  • Remaining count: Shows how many items are not yet complete
  • Selection tracking: .OnSelectionChanged() tracks which list item is selected
  • Delete functionality: Removes the selected item
  • Multiple event handlers: Chain .OnSelectionChanged() and .OnItemActivated() for different behaviors

What You've Learned

Through this tutorial, you've learned:

✅ How to create a console application and install Hex1b
✅ The fluent API using context (ctx) to build widgets
✅ State management with simple classes
✅ Layout with Border, VStack, and HStack
✅ Interactive widgets: Button, TextBox, List
✅ Event handling for user input
✅ Collection expressions for composing UIs

Next Steps

Released under the MIT License.