Notifications

SharpConsoleUI ships two complementary notification systems, both reached through the ConsoleWindowSystem instance:

  • ToastService — lightweight, non-blocking single-line overlays that stack in a screen corner and auto-dismiss. Best for transient feedback ("Saved", "Sync started").
  • NotificationStateService — title + message notifications that can be modal/blocking. Best for messages the user must read or acknowledge.

Table of Contents

Choosing a system

ToastService NotificationStateService
Access ws.ToastService ws.NotificationStateService
Blocks input Never Optional (blockUi: true)
Layout Single line, anchored to a corner, auto-stacks Title + message, centered or attached to a window
Dismissal Auto-timeout, click-to-dismiss, or sticky Auto-timeout or explicit dismiss
Typical use Transient status ("Saved", "Sync started") Errors/confirmations the user must read

Both use the same NotificationSeverity levels.

ToastService

ToastService (SharpConsoleUI.Core) manages transient toast overlays: showing, stacking, and auto-dismissing. It is rendered as a desktop portal and never steals focus or blocks input.

Quick start

// Simplest form — message + severity, default options
ws.ToastService.Show("Saved successfully", NotificationSeverity.Success);
ws.ToastService.Show("Sync started", NotificationSeverity.Info);
ws.ToastService.Show("Disk space is low", NotificationSeverity.Warning);

Show returns a string id you can keep to dismiss the toast later.

Severities

Each severity supplies an icon and an accent color. The toast border and a matching inner accent bar follow the severity's color role.

public enum NotificationSeverityEnum
{
    Danger,   // ✘  red    — error conditions (default timeout 6s)
    Info,     // ●  blue   — informational
    None,     //    none   — generic, no icon
    Success,  // ✔  green  — successful operations
    Warning   // ▲  yellow — warnings
}

Pass one of the static NotificationSeverity instances: NotificationSeverity.Success, .Info, .Warning, .Danger, .None.

Positions and stacking

Toasts anchor to one of five positions and stack away from the anchored edge with no gap between them:

public enum ToastPosition
{
    BottomRight,  // default
    TopRight,
    BottomLeft,
    TopLeft,
    BottomCenter
}

Set a process-wide default, or override per toast via ToastOptions:

ws.ToastService.DefaultPosition = ToastPosition.TopRight;

ws.ToastService.Show("Anchored top-right", NotificationSeverity.Info,
    new ToastOptions(Position: ToastPosition.TopRight));

Multiple toasts stack automatically; the service reflows the stack as toasts are added and dismissed.

ToastOptions

ToastOptions is an immutable record overriding per-toast behavior. All members are optional:

public sealed record ToastOptions(
    int? Timeout = null,            // auto-dismiss in ms; null = severity default
    bool Sticky = false,           // true = never auto-dismiss
    ToastPosition? Position = null  // null = service DefaultPosition
);
// Custom timeout
ws.ToastService.Show("Uploading…", NotificationSeverity.Info,
    new ToastOptions(Timeout: 10_000));

// Sticky — stays until clicked or DismissAll()
ws.ToastService.Show("Connection lost", NotificationSeverity.Danger,
    new ToastOptions(Sticky: true));

Dismissing

string id = ws.ToastService.Show("Working…", NotificationSeverity.Info,
    new ToastOptions(Sticky: true));

ws.ToastService.Dismiss(id);   // returns false if no matching toast
ws.ToastService.DismissAll();  // clears every active toast at once

Non-sticky toasts also dismiss automatically after their timeout, and clicking a toast dismisses it.

ToastService implements IDisposable; Dispose() calls DismissAll().

Observing state

The service is observable, which makes it easy to mirror toast state into a status bar, badge, or test:

// INotifyPropertyChanged for HasToasts, ActiveCount, DefaultPosition
ws.ToastService.PropertyChanged += (_, e) => { /* e.PropertyName */ };

// Observable collection of the currently visible toasts
ws.ToastService.ActiveToasts;   // ObservableCollection<ToastInfo>

// Immutable snapshot: ActiveToasts + TotalShown + TotalDismissed counters
ToastState state = ws.ToastService.CurrentState;
bool any = ws.ToastService.HasToasts;
int count = ws.ToastService.ActiveCount;

// Events
ws.ToastService.ToastShown        += (_, e) => { /* e.Toast, e.PreviousState, e.CurrentState */ };
ws.ToastService.ToastDismissed    += (_, e) => { /* e.Toast, ... */ };
ws.ToastService.AllToastsDismissed += (_, _) => { };
ws.ToastService.StateChanged      += (_, state) => { };

ToastInfo exposes Id, Message, Severity, Position, and Sticky.

Defaults and tuning

Toast defaults live in Configuration.ControlDefaults:

Constant Default Meaning
ToastDefaultTimeoutMs 3000 Auto-dismiss for non-error toasts
ToastErrorTimeoutMs 6000 Auto-dismiss for Danger toasts
ToastMaxWidth 48 Max toast width, in columns
ToastEdgeMargin 1 Margin between the stack and the screen edge
ToastGap 0 Vertical gap between stacked toasts

Theming

Toast borders and the inner accent bar resolve from the severity's color role against the active theme, so toasts adapt to light/dark themes automatically. See THEMES.md for the role palette.

Demo

A runnable showcase of every option lives at Examples/DemoApp/DemoWindows/ToastsWindow.cs (severities, positions, sticky, stacking, dismiss-all).

NotificationStateService

NotificationStateService manages title + message notifications, including modal ones that block the UI until dismissed. Reach it via ws.NotificationStateService.

Showing notifications

string ShowNotification(
    string title,
    string message,
    NotificationSeverity severity,
    bool blockUi = false,
    int? timeout = 5000,            // null = no auto-dismiss
    Window? parentWindow = null
);
// Auto-dismisses after 5 seconds
ws.NotificationStateService.ShowNotification(
    "File Saved", "Your document has been saved.", NotificationSeverity.Success);

// Persistent + modal — user must dismiss, UI is blocked meanwhile
ws.NotificationStateService.ShowNotification(
    "Error", "Failed to connect to database.",
    NotificationSeverity.Danger, blockUi: true, timeout: null);

// Custom timeout
ws.NotificationStateService.ShowNotification(
    "Processing", "Operation in progress…",
    NotificationSeverity.Info, timeout: 10_000);

// Attached to a specific window
ws.NotificationStateService.ShowNotification(
    "Validation Error", "Please fill in all required fields.",
    NotificationSeverity.Warning, parentWindow: formWindow);

Dismissing notifications

string id = ws.NotificationStateService.ShowNotification(
    "Info", "Message", NotificationSeverity.Info);

ws.NotificationStateService.DismissNotification(id);
ws.NotificationStateService.DismissAllNotifications();

State and events

NotificationState State = ws.NotificationStateService.State;
bool any   = ws.NotificationStateService.HasNotifications;
int count  = ws.NotificationStateService.ActiveCount;

NotificationInfo? info = ws.NotificationStateService.GetNotification(id);

ws.NotificationStateService.NotificationShown     += (_, e) => { /* e.Notification.Title */ };
ws.NotificationStateService.NotificationDismissed += (_, e) => { };

See also