ButtonWidget
An interactive button that responds to Enter, Space, or mouse clicks.
Buttons are focusable widgets that provide visual feedback when focused and can trigger actions when activated. They're the primary way to let users perform actions in your terminal UI.
Basic Usage
Create buttons using the fluent API and handle clicks with OnClick:
dotnet runNavigation
Use Tab to move focus between buttons, and Enter or Space to activate the focused button. Mouse clicks also work in supported terminals.
Click Handlers
The OnClick method accepts both synchronous and asynchronous handlers.
Synchronous Handler
For simple state updates, use a synchronous handler:
v.Button("Save").OnClick(_ => document.Save())The handler receives ButtonClickedEventArgs with access to:
Widget- The source ButtonWidgetNode- The underlying ButtonNodeContext- Access to the application context for navigation, stop requests, etc.
Asynchronous Handler
Hex1b also supports async handlers for when you need to call async APIs:
v.Button("Fetch").OnClick(async _ => {
var data = await httpClient.GetStringAsync(url);
state.Data = data;
})2
3
4
Render Loop Blocking
Click handlers execute within the render loop. While your async handler awaits, the UI cannot update—no re-renders, no input processing. For quick async calls (like a single HTTP request), this is usually acceptable. For longer operations, use the background work pattern shown below.
Background Work Pattern
For operations that take noticeable time, trigger the work from your state object and use app.Invalidate() to request re-renders as progress updates:
dotnet runKey points in this pattern:
- Fire and forget: The click handler calls
StartLoading()synchronously—it doesn't await the background work - State owns the work: The
LoaderStateclass manages the async operation internally - Explicit invalidation: Call
app.Invalidate()whenever state changes to trigger a re-render - UI stays responsive: The render loop continues while work happens in the background
When to Use Each Approach
- Synchronous: Simple state updates (incrementing counters, toggling flags)
- Async handler: Quick async calls where brief blocking is acceptable (single API call)
- Background work: Long-running operations, multi-step processes, or anything needing progress updates
Counter Example
Here's a more complete example showing multiple buttons controlling shared state:
dotnet runFocus Behavior
Buttons visually indicate their focus state:
| State | Appearance |
|---|---|
| Unfocused | [ Label ] |
| Focused | Highlighted with theme colors |
The focused button has a distinct background color (configurable via theming) to make it clear which button will be activated when pressing Enter or Space.
Focus Navigation
- Tab - Move focus to the next focusable widget
- Shift+Tab - Move focus to the previous focusable widget
- Enter or Space - Activate the focused button
- Mouse click - Focus and activate the button
Theming
Customize button appearance using theme elements:
using Hex1b;
using Hex1b.Theming;
var theme = new Hex1bTheme("Custom")
.Set(ButtonTheme.ForegroundColor, Hex1bColor.White)
.Set(ButtonTheme.BackgroundColor, Hex1bColor.Blue)
.Set(ButtonTheme.FocusedForegroundColor, Hex1bColor.Black)
.Set(ButtonTheme.FocusedBackgroundColor, Hex1bColor.Yellow)
.Set(ButtonTheme.LeftBracket, "< ")
.Set(ButtonTheme.RightBracket, " >");
await using var terminal = Hex1bTerminal.CreateBuilder()
.WithHex1bApp((app, options) =>
{
options.Theme = theme;
return ctx => ctx.Button("Themed Button");
})
.Build();
await terminal.RunAsync();2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Available Theme Elements
| Element | Type | Default | Description |
|---|---|---|---|
ForegroundColor | Hex1bColor | Default | Text color when unfocused |
BackgroundColor | Hex1bColor | Default | Background when unfocused |
FocusedForegroundColor | Hex1bColor | Black | Text color when focused |
FocusedBackgroundColor | Hex1bColor | White | Background when focused |
LeftBracket | string | "[ " | Left bracket decoration |
RightBracket | string | " ]" | Right bracket decoration |
MinimumWidth | int | 10 | Minimum button width |
Input Bindings
Buttons automatically register these input bindings when a click handler is set:
| Input | Action |
|---|---|
| Enter | Activate button |
| Space | Activate button |
| Left mouse click | Activate button |
You can add additional bindings using the standard input binding API:
v.Button("Save")
.OnClick(_ => Save())
.WithInputBindings(bindings => bindings
.Ctrl().Key(Hex1bKey.S).Action(() => Save()))2
3
4
Related Widgets
- TextWidget - For non-interactive text display
- TextBoxWidget - For text input
- HyperlinkWidget - For clickable links
- ListWidget - For selectable lists of items