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
vimorhtop. 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 initordotnet 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 MyTodoAppInstall 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:
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:
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
CounterStateclass 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:
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:
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:
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
- Widgets & Nodes - Understand the core architecture
- Layout System - Master the constraint-based layout
- Input Handling - Learn about keyboard shortcuts and focus
- Theming - Customize the appearance of your app