Notifications
The notification system provides a way to display transient messages to users. Notifications appear as floating cards in the corner of the screen and can include action buttons, timeouts, and a slide-out drawer for reviewing all notifications.
Overview
The notification system consists of:
| Component | Purpose |
|---|---|
NotificationPanel | Container that hosts content and displays notifications |
NotificationIcon | Bell icon (🔔) that shows count and toggles the drawer |
Notification | The notification data with title, body, actions, and lifecycle |
NotificationStack | Manages the collection of active notifications |
Basic Setup
To enable notifications in your app, wrap your content in a NotificationPanel inside a ZStack:
dotnet runLayout Pattern
The ZStack → VStack → NotificationPanel pattern ensures notifications float above your content. Place the NotificationIcon in your header/menu bar for easy access.
Posting Notifications
Post notifications from any event handler using e.Context.Notifications.Post():
ctx.Button("Save").OnClick(e => {
SaveFile();
e.Context.Notifications.Post(
new Notification("Saved!", "File saved successfully"));
})2
3
4
5
Notification Properties
Create notifications with a title and optional body:
// Title only
new Notification("Operation complete")
// Title and body
new Notification("Download Complete", "file.zip (2.4 MB) downloaded successfully")2
3
4
5
Timeouts
By default, notifications stay visible until dismissed. Add a timeout to auto-hide them:
new Notification("Auto-save", "Document saved")
.Timeout(TimeSpan.FromSeconds(5))2
When a notification times out:
- It disappears from the floating view
- It remains in the notification drawer
- The
OnTimeouthandler is called (if set)
Permanent Notifications
Omit .Timeout() for notifications that require user attention:
// This stays visible until the user dismisses it
new Notification("Action Required", "Please review the pending changes")
.PrimaryAction("Review", async ctx => OpenReview())2
3
Action Buttons
Notifications can have a primary action and multiple secondary actions displayed in a split button:
dotnet runPrimary Action
The main action button, prominently displayed:
new Notification("New Message", "You have a new message from Alice")
.PrimaryAction("Read", async ctx => {
await OpenMessageAsync();
ctx.Dismiss(); // Optionally dismiss after action
})2
3
4
5
Secondary Actions
Additional actions appear in a dropdown menu:
new Notification("File Updated", "config.json was modified")
.PrimaryAction("View Diff", async ctx => ViewDiff())
.SecondaryAction("Revert", async ctx => RevertChanges())
.SecondaryAction("Ignore", async ctx => ctx.Dismiss())2
3
4
Action Context
Action handlers receive a NotificationActionContext with:
| Property | Description |
|---|---|
Notification | The notification this action belongs to |
CancellationToken | Token for cancelling async operations |
InputTrigger | Access to app services (focus, popups, etc.) |
Dismiss() | Remove this notification from the stack |
.PrimaryAction("Open", async ctx => {
await OpenFileAsync(ctx.CancellationToken);
ctx.Dismiss(); // Remove notification after action
})2
3
4
Lifecycle Events
Handle notification lifecycle events with OnTimeout and OnDismiss:
dotnet runOnTimeout
Called when the notification auto-hides after its timeout:
new Notification("Reminder", "Meeting in 5 minutes")
.Timeout(TimeSpan.FromMinutes(5))
.OnTimeout(async ctx => {
// Log that the user saw the reminder
Analytics.Track("reminder_shown");
})2
3
4
5
6
OnDismiss
Called when the notification is dismissed (by user or programmatically):
new Notification("Undo Available", "Item deleted")
.Timeout(TimeSpan.FromSeconds(10))
.OnDismiss(async ctx => {
// Cleanup - the undo window has closed
await PermanentlyDeleteAsync();
})2
3
4
5
6
The Notification Drawer
The drawer shows all notifications (both active and timed-out). Access it by:
- Clicking the notification icon (🔔)
- Pressing Alt+N from anywhere in the app
When the drawer opens:
- Floating notifications move into the drawer
- All notifications are visible in a scrollable list
- Click outside or press Escape to close
Drawer Behavior
| Setting | Method | Description |
|---|---|---|
| Float over content | .WithDrawerFloats(true) | Drawer overlays content (default) |
| Push content aside | .WithDrawerFloats(false) | Content resizes when drawer opens |
v.NotificationPanel(content)
.WithDrawerFloats(false) // Drawer docks instead of floating2
Notification Icon
The NotificationIcon displays the bell and notification count:
bar.NotificationIcon()Customization
// Custom bell character (for terminals without emoji support)
bar.NotificationIcon("*")
// Hide the count badge
bar.NotificationIcon().WithCount(false)2
3
4
5
Panel Configuration
Configure the notification panel behavior:
v.NotificationPanel(content)
.WithMaxFloating(5) // Show up to 5 floating notifications (default: 3)
.WithOffset(4, 2) // Offset from corner (x=4, y=2)
.WithAnimation(false) // Disable progress bar animation
.WithDrawerFloats(true) // Drawer floats over content2
3
4
5
Configuration Options
| Method | Default | Description |
|---|---|---|
WithMaxFloating(n) | 3 | Maximum floating notifications visible |
WithOffset(x, y) | (2, 1) | Offset from top-right corner |
WithAnimation(bool) | true | Animate timeout progress bars |
WithDrawerFloats(bool) | true | Drawer floats vs pushes content |
Keyboard Shortcuts
| Key | Action |
|---|---|
| Alt+N | Toggle notification drawer |
| Escape | Close drawer (when open) |
| Tab | Navigate between notification buttons |
| Enter | Activate focused button |
Notification Card Layout
Each notification card displays:
┌────────────────────────────────────┐
│ Title [ × ] │ ← Dismiss button
│ Body text (if present) │
│ [ Primary ▼ ] │ ← Action button (if present)
│ ▓▓▓▓▓▓▓▓▓░░░░░ │ ← Timeout progress (floating only)
└────────────────────────────────────┘2
3
4
5
6
- Title: Always displayed prominently
- Body: Optional additional text
- Dismiss (×): Removes the notification entirely
- Action button: Split button for primary + secondary actions
- Progress bar: Shows time remaining (floating cards only)
Complete Example
Here's a complete example showing all notification features:
using Hex1b;
await using var terminal = Hex1bTerminal.CreateBuilder()
.WithHex1bApp((app, options) => ctx => ctx.ZStack(z => [
z.VStack(v => [
// Header with notification icon
v.HStack(bar => [
bar.Button("File"),
bar.Button("Edit"),
bar.Text("").FillWidth(),
bar.NotificationIcon()
]),
// Main content wrapped in notification panel
v.NotificationPanel(
v.VStack(content => [
content.Button("Save").OnClick(e => {
e.Context.Notifications.Post(
new Notification("Saved", "Document saved")
.Timeout(TimeSpan.FromSeconds(3)));
}),
content.Button("Delete").OnClick(e => {
e.Context.Notifications.Post(
new Notification("Deleted", "Item moved to trash")
.Timeout(TimeSpan.FromSeconds(10))
.PrimaryAction("Undo", async ctx => {
// Restore the item
ctx.Dismiss();
})
.OnDismiss(async ctx => {
// Permanently delete after dismiss
}));
}),
content.Button("Error").OnClick(e => {
// No timeout - requires user action
e.Context.Notifications.Post(
new Notification("Error", "Operation failed")
.PrimaryAction("Retry", async ctx => {
// Retry the operation
ctx.Dismiss();
})
.SecondaryAction("View Details", async ctx => {
// Show error details
}));
})
])
).Fill()
])
]))
.Build();
await terminal.RunAsync();2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
Related Widgets
- Button - Trigger notifications from button clicks
- SplitButton - Used for notification action buttons
- InfoBar - Persistent status information