Skip to content

Toast

Transient notifications stacked in a corner. Toast is one message, ToasterState is the stack your app keeps, and ToasterWidget draws it.

rust
use ratcn::{Toast, ToasterState, ToasterWidget};

// In update(), when something happens:
state.toasts.push(Toast::success("Saved"), now);

// In draw(), after everything else, so toasts sit on top:
frame.render_widget(
    ToasterWidget::new(&state.toasts, now).themed(&theme),
    frame.area(),
);

Toasts take no focus and handle no events. They are paint-only, so they work in a plain Ratatui app with no Ratcn runtime.

Kinds

The kind sets the accent color and icon. Each has a shorthand constructor, or pass one to .kind(...).

KindShorthandUse for
DefaultToast::newNeutral news.
SuccessToast::successSomething worked.
ErrorToast::errorSomething failed. Consider .persistent().
WarningToast::warningNeeds attention, but did not fail.
InfoToast::infoNeutral information, accented to stand out.
LoadingToast::loadingWork in progress. Usually persistent.
rust
Toast::error("Upload failed")
    .description("Check your connection and try again.")
    .persistent()

.description(...) adds a second line under the title, and .border(false) drops the border on one toast.

Your app owns the clock

Ratcn never calls Instant::now. Every method that cares about time takes a Duration from you, which is what lets toasts work in the browser and be tested without sleeping.

The loop is three steps:

rust
// 1. Push with the current reading from your clock.
state.toasts.push(Toast::success("Saved"), now);

// 2. Ask when to wake up next, and wait that long.
match state.toasts.time_until_next_expiry(now) {
    Some(timeout) => poll_for_input(timeout)?,
    None => wait_for_input()?, // nothing expires; block until input
}

// 3. When the timer fires, drop what expired and redraw if anything changed.
if state.toasts.prune_expired(now) {
    redraw();
}

Skip step 2 and nothing ever disappears — the stack only changes when you tell it time has passed. In a browser loop that redraws continuously, pruning once per frame is enough.

Toasts expire after 4 seconds by default. .duration(...) changes that and .persistent() disables it.

ToasterState can prune expired entries or clear the entire stack, including persistent toasts. It does not dismiss or replace one entry by identity. If an app needs individual lifecycle control, it can own a custom collection of ToastEntry values and render that collection with ToasterWidget::from_entries(...).

Placement

.position(...) picks the corner or edge; the stack grows away from it, so the newest toast is always nearest. .width(...), .gap(...), and .offset(x, y) size and inset the stack, and .visible_toasts(n) caps how many show at once — older ones stay in the state and still expire on schedule.

rust
use ratcn::ToastPosition;

ToasterWidget::new(&state.toasts, now)
    .themed(&theme)
    .position(ToastPosition::TopRight)
    .visible_toasts(5)

Positions are TopLeft, TopCenter, TopRight, BottomLeft, BottomCenter, and BottomRight. ToastPosition::is_top() reports which way a stack grows, if your own layout needs to know.

Sizing

Toasts have no fixed height — the title and description wrap at the stack width and the widget measures the result. If the area cannot hold every visible toast, the newest that fit whole are drawn and the rest wait for the next frame. A toast is never clipped mid-content and toasts never overlap.

Styling

.themed(&theme) derives every color. .style(ToasterStyle) takes explicit ones — one surface and border shared by all toasts, plus an accent per kind.

rust
use ratcn::ToasterStyle;

let mut style = ToasterStyle::from_theme(&theme);
style.error = theme.accent;

ToasterWidget::new(&state.toasts, now).style(style)

Notes

  • ToasterState::clear() dismisses everything, including persistent toasts; len() and is_empty() report the stack size, useful for reserving space.
  • Toast and ToastEntry expose read accessors (title, description_text, toast_kind, has_border, created_at, age, is_expired) for writing your own renderer against entries().
  • from_entries(...) renders a bare ToastEntry slice, for apps that keep toasts in their own structure for individual lifecycle control. .visible() iterates what would be drawn.
  • ToasterStyle::fallback() is the no-theme palette, for plain ANSI terminals.
  • The widget skips expired toasts while drawing, so a late prune never shows a stale one.

Full API

Every method, with parameter and edge-case detail: Toast, ToastKind, ToasterState, ToastEntry, ToasterWidget, ToastPosition, ToasterStyle.

See Also

Loop and backend wiring, including the browser case: Host Integration.