Toast
Transient notifications stacked in a corner.
use herogpui::components::toast::{Toast, ToastViewport};Toast on docs.rsSource at v0.14.0
Usage
Showing Usage, live: HeroGPUI compiled to WebAssembly. The canvas has no accessibility tree; each example's description and code below carry the same information.
An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{Button, Toast, ToastViewport, Variant};
use herogpui::gpui::prelude::*;
Button::new("toast-usage")
.label("Show a toast")
.variant(Variant::Secondary)
.on_press(|_, _, cx| {
Toast::new("Saved")
.description("Your changes are live.")
.closable(true)
.push(Some(std::time::Duration::from_secs(4)), cx);
})An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{Button, Color, Size, Toast, ToastViewport, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_wrap()
.items_center()
.justify_center()
.gap(px(12.))
.children(Color::ALL
.iter()
.map(|c| {
let color = *c;
Button::new(format!("toast-v-{c:?}"))
.label(c.label())
.variant(Variant::Secondary)
.size(Size::Sm)
.on_press(move |_, _, cx| {
Toast::new(format!("{} toast", color.label()))
.description("One variant per status colour.")
.variant(color)
.closable(true)
.push(Some(std::time::Duration::from_secs(4)), cx);
})
}))The viewport decides where the stack sits. This gallery mounts one `ToastViewport` in its shell; each button moves it and pushes a toast into that corner. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{Button, Size, Toast, ToastPlacement, ToastViewport, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_wrap()
.items_center()
.justify_center()
.gap(px(12.))
.children([
("TopStart", ToastPlacement::TopStart),
("Top", ToastPlacement::Top),
("TopEnd", ToastPlacement::TopEnd),
("BottomStart", ToastPlacement::BottomStart),
("Bottom", ToastPlacement::Bottom),
("BottomEnd", ToastPlacement::BottomEnd),
]
.into_iter()
.map(|(label, placement)| {
Button::new(format!("toast-pl-{label}"))
.label(label)
.variant(Variant::Tertiary)
.size(Size::Sm)
// Move the viewport, then push into it: six
// buttons that all pushed into the same corner
// showed nothing about `placement`.
.on_press(cx.listener(move |this, _, _, cx| {
this.toast_placement = placement;
Toast::new(label)
.description("Pushed into the shell's viewport.")
.closable(true)
.push(Some(std::time::Duration::from_secs(3)), cx);
cx.notify();
}))
.into_any_element()
}))An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{Button, Size, Toast, ToastViewport, Variant};
use herogpui::gpui::prelude::*;
Button::new("toast-simple")
.label("Title only")
.variant(Variant::Secondary)
.size(Size::Sm)
.on_press(|_, _, cx| {
Toast::new("Copied to the clipboard")
.push(Some(std::time::Duration::from_secs(3)), cx);
})Toast titles remain readable when they need more than one line. The close affordance is positioned outside the content flow, so wrapping does not steal width from the message. An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{Button, Size, Toast, ToastViewport, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_wrap()
.items_center()
.justify_center()
.gap(px(12.))
.children(vec![Button::new("toast-long-title-push")
.label("Push a long title")
.variant(Variant::Secondary)
.size(Size::Sm)
.on_press(|_, _, cx| {
Toast::new(
"A notification title that intentionally spans multiple lines so the Toast card keeps all content readable",
)
.description("The close button stays anchored to the card corner.")
.push(Some(std::time::Duration::from_secs(8)), cx);
})
.into_any_element()])The close affordance stays out of the message flow, fades in when the frontmost card is hovered or keyboard-focused, and ignores pointer activation while hidden. An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{Button, Size, Toast, ToastViewport, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_wrap()
.items_center()
.justify_center()
.gap(px(12.))
.children(vec![Button::new("toast-close-reveal-push")
.label("Push persistent toast")
.variant(Variant::Secondary)
.size(Size::Sm)
.on_press(|_, _, cx| {
Toast::new("Hover or tab to reveal close")
.description("The close control fades in at the card corner.")
.closable(true)
.timeout(std::time::Duration::ZERO)
.push(None, cx);
})
.into_any_element()])The variant picks the glyph โ success shows a tick, danger a crossed circle. `indicator` overrides it with an asset path, `indicator_content` renders caller-owned GPUI content inside the same box, and `indicator(None)` removes the glyph entirely. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ActiveTheme, Button, Color, Size, Toast, ToastViewport, Variant, icons};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_wrap()
.items_center()
.justify_center()
.gap(px(12.))
.children(vec![
Button::new("toast-ind-success")
.label("Success")
.variant(Variant::Secondary)
.size(Size::Sm)
.on_press(|_, _, cx| {
Toast::success("Deployed")
.description("Build 412 is live.")
.closable(true)
.push(None, cx);
})
.into_any_element(),
Button::new("toast-ind-danger")
.label("Danger")
.variant(Variant::Secondary)
.size(Size::Sm)
.on_press(|_, _, cx| {
Toast::error("Deploy failed")
.description("Two tests did not pass.")
.closable(true)
.push(None, cx);
})
.into_any_element(),
Button::new("toast-ind-custom")
.label("Custom glyph")
.variant(Variant::Secondary)
.size(Size::Sm)
.on_press(|_, _, cx| {
Toast::new("One new message")
.description("From martha@example.com.")
.indicator(SharedString::from(icons::MAIL))
.push(None, cx);
})
.into_any_element(),
Button::new("toast-ind-custom-content")
.label("Custom content")
.variant(Variant::Secondary)
.size(Size::Sm)
.on_press(|_, _, cx| {
Toast::new("Custom indicator content")
.description("The render factory owns this icon.")
.variant(Color::Accent)
.indicator_content(|cx| gpui::svg()
.size(px(16.))
.path(icons::INFO_CIRCLE)
.text_color(cx.colors().foreground)
.into_any_element())
.push(None, cx);
})
.into_any_element(),
Button::new("toast-ind-none")
.label("No glyph")
.variant(Variant::Secondary)
.size(Size::Sm)
.on_press(|_, _, cx| {
Toast::success("Saved").indicator(None).push(None, cx);
})
.into_any_element(),
])A toast is a title, a description and a status. Anything richer is the caller's own panel: this example renders its own body inside the queue's slot. An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{Button, Color, Size, Toast, ToastViewport, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_wrap()
.items_center()
.justify_center()
.gap(px(12.))
.children(vec![Button::new("toast-custom")
.label("Push a two-line toast")
.variant(Variant::Secondary)
.size(Size::Sm)
.on_press(|_, _, cx| {
Toast::new("Jane invited you")
.description("Acme workspace \u{2014} Owner")
.variant(Color::Accent)
.closable(true)
.push(Some(std::time::Duration::from_secs(5)), cx);
})
.into_any_element()])`toast.promise` shows a loading toast while the work runs, then updates it in place. Ok is success, Err is danger, and the default dismiss clock starts at settle. `promise_task` lets the gallery own the pending work and cancel it when resetting the demo. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{Button, Size, Toast, ToastViewport, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_wrap()
.items_center()
.justify_center()
.gap(px(12.))
.children(vec![
Button::new("toast-promise")
.label("Upload a file")
.variant(Variant::Secondary)
.size(Size::Sm)
.on_press(cx.listener(|this, _, _, cx| {
let executor = cx.background_executor().clone();
let (_, task) = Toast::promise_task(
async move {
executor
.timer(std::time::Duration::from_millis(1500))
.await;
Ok("document.pdf uploaded \u{2014} 1 KB".into())
},
"Uploading\u{2026}",
cx,
);
this.track_toast_promise(task);
}))
.into_any_element(),
Button::new("toast-promise-error")
.label("Create event (error)")
.variant(Variant::Secondary)
.size(Size::Sm)
.on_press(cx.listener(|this, _, _, cx| {
let executor = cx.background_executor().clone();
let (_, task) = Toast::promise_task(
async move {
executor
.timer(std::time::Duration::from_millis(1500))
.await;
Err("The date is in the past".into())
},
"Creating event\u{2026}",
cx,
);
this.track_toast_promise(task);
}))
.into_any_element(),
])`onClose` runs however the toast goes -- dismissed by hand or timed out -- so the count follows the toast, not the button. An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{ActiveTheme, Button, Size, Toast, ToastViewport, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{App, px};
use herogpui::gpui;
let toast_closed = toasts_closed(cx);
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
para(&format!("Toasts closed so far: {toast_closed}"), cx),
gpui::div()
.flex()
.flex_wrap()
.items_center()
.justify_center()
.gap(px(12.))
.children(vec![Button::new("toast-callback")
.label("Push a closable toast")
.variant(Variant::Secondary)
.size(Size::Sm)
.on_press(|_, _, cx| {
Toast::new("Dismiss me")
.description("Or wait four seconds.")
.closable(true)
.on_close(bump_toast_closed)
.push(None, cx);
})
.into_any_element()])
.into_any_element(),
])Also calls ToastsClosed, toasts_closed, para, bump_toast_closed (included when you copy).
Hover or focus the stack to expand it. `isExpanded` keeps it open; timers still run unless the pointer or focus is inside. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{Button, Size, Toast, ToastViewport, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
gpui::div()
.flex()
.flex_wrap()
.items_center()
.justify_center()
.gap(px(12.))
.children(vec![
Button::new("toast-stack-push")
.label("Push three")
.variant(Variant::Secondary)
.size(Size::Sm)
.on_press(|_, _, cx| {
for n in 1..=3 {
Toast::new(format!("Stacked {n}"))
.description("Hover the stack to expand.")
.timeout(std::time::Duration::from_secs(8))
.push(None, cx);
}
})
.into_any_element(),
Button::new("toast-stack-keep")
.label(if self.demo_flag("toast-expanded", false) {
"Stop forcing open"
} else {
"Keep expanded"
})
.variant(Variant::Tertiary)
.size(Size::Sm)
.on_press(cx.listener(|this, _, _, cx| {
let next = !this.demo_flag("toast-expanded", false);
this.set_demo_flag("toast-expanded", next);
cx.notify();
}))
.into_any_element(),
])
.into_any_element(),
])`maxVisibleToasts` caps visibility without dropping overflow: the ones past the cap wait their turn. Push four and watch one queue. An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{Button, Size, Toast, ToastViewport, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_wrap()
.items_center()
.justify_center()
.gap(px(12.))
.children(vec![Button::new("toast-queue")
.label("Push four")
.variant(Variant::Secondary)
.size(Size::Sm)
.on_press(|_, _, cx| {
for n in 1..=4 {
Toast::new(format!("Message {n}"))
.description("Three are visible at a time.")
.push(Some(std::time::Duration::from_secs(3)), cx);
}
})
.into_any_element()])A toast needs a viewport somewhere in the tree. This gallery mounts one in its shell, which is why every page can push. Statements with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{Color, Toast, ToastPlacement, ToastViewport};
use herogpui::gpui::div;
use herogpui::gpui::prelude::*;
use std::time::Duration;
// Once, in the shell: a viewport beside the page content.
let page = div().child("Page content");
let shell = div()
.child(page)
.child(
ToastViewport::new()
.placement(ToastPlacement::BottomEnd)
.max_visible_toasts(2),
);
// Anywhere, afterwards:
Toast::new("Saved")
.description("Your changes are live.")
.variant(Color::Success)
.closable(true)
.push(Some(Duration::from_secs(4)), cx);An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{Button, Color, Size, Toast, ToastViewport, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_wrap()
.items_center()
.justify_center()
.gap(px(12.))
.children(Color::ALL
.iter()
.map(|c| {
let color = *c;
Button::new(format!("toast-{c:?}"))
.label(c.label())
.variant(Variant::Secondary)
.size(Size::Sm)
.on_press(move |_, _, cx| {
Toast::new(format!("{} toast", color.label()))
.description("Pushed from the gallery.")
.variant(color)
.closable(true)
.push(Some(std::time::Duration::from_secs(4)), cx);
})
}))Helpers used by these examples
Plain functions over the public API, defined once here and copied with each example that calls them.
struct ToastsClosed(usize);
impl gpui::Global for ToastsClosed {}fn toasts_closed(cx: &App) -> usize {
cx.try_global::<ToastsClosed>().map_or(0, |c| c.0)
}fn para(text: &str, cx: &App) -> gpui::AnyElement {
let colors = cx.colors();
gpui::div()
.text_size(px(14.5))
.line_height(px(24.))
.text_color(colors.foreground)
.max_w(px(720.))
.child(text.to_owned())
.into_any_element()
}fn bump_toast_closed(cx: &mut App) {
let next = cx.try_global::<ToastsClosed>().map_or(0, |c| c.0) + 1;
cx.set_global(ToastsClosed(next));
cx.refresh_windows();
}Anatomy
Rust types
Toast is assembled from these builders. The API reference lists each one.
Customization
Shared appearance belongs on ThemeBuilder::components and .recipe("name"). Typed builders change how Toast looks at one call site. Every builder also carries sx for placement, width and flex: it takes GPUI's own styling methods and refines them over the component's root after the theme, so an instance override wins. Behaviour builders live in the API reference below.
Styling
| Method | Values | Description |
|---|---|---|
placement(ToastPlacement) | top start | top | top end | bottom start | bottom | bottom end | Positions the application-wide viewport at one of six window anchors. |
variant(Color) | default | accent | success | warning | danger | Selects the semantic title and indicator colors. |
variant(Color) / success / error | ToastVariant | Plain Toast::new now matches the default variant; semantic variants use the builder or constructors. |
// The one slot for caller-owned low-level styling: GPUI's
// styling methods, refined over the root element last.
.sx(|el| {
el.bg(gpui::rgba(0xffa500ff)) // background
.text_color(gpui::rgba(0x000000ff)) // text
.w(gpui::px(13.)) // size
.h(gpui::px(12.))
})API reference
Against HeroUI v3.2.6: 46 entries ported, 12 partial, 10 not ported. Partial rows are marked in the tables; entries not ported are listed under each table with the reason.
Builders
| Builder | Values | Default | Description |
|---|---|---|---|
id( | string | โ | Optional GPUI identity. A named viewport reports role=region named "{n} notifications."; an unnamed one produces no landmark. |
placement( | top start | | ToastPlacement:: | Positions the application-wide viewport at one of six window anchors. |
gap( | number | 12 | Sets the distance between rendered stack entries. |
max_visible_toasts( | number | 3 | Limits rendered cards without evicting or pausing queued entries. Overflow stays mounted as hidden cards. |
is_expanded( | true | | false | Forces the stack open when more than one toast is active. Hover and focus-within still expand when this is false; the prop does not pause timers. |
exit_duration( | number | 300 | Keeps a dismissed card mounted for the exit fade. Duration zero and reduced motion remove it immediately. |
hotkey( | string[] | ['Alt', | Focuses the region so the stack expands. ToastHotkey::disabled turns the shortcut off. Extra modifiers must not be held. |
scale_factor(Partial | number | 0. | Geometrically narrows cards by stack depth because GPUI div transforms are unavailable. |
width( | number | | 460 | Sets the desktop card width in pixels; the rendered width clamps to the live viewport minus both insets on narrow windows. |
toast::Partial | ToastQueue< | โ | Uses one application-global ToastStore rather than accepting a viewport-local queue. |
push( | QueuedToast< | QueuedToast:: | ToastData carries the queue id, content, handlers, state, and variant. |
variant( | default | | Color:: | Selects the semantic title and indicator colors. |
title / description / indicator / action / closablePartial | AnyElement | โ | The monolithic builder composes indicator, text, action, and close parts from typed data. |
new(Partial | AnyElement | โ | Title and optional description are composed in the built-in content column. |
indicator( | AnyElement | Option:: | A caller can replace or suppress the icon by asset path, or render fresh GPUI content through the shared indicator box. |
new( | AnyElement | โ | Required title text is supplied to the constructor. |
description( | AnyElement | โ | Optional description text is composed below the title. |
action(Partial | AnyElement | โ | A text label and press handler create the action button and dismiss its toast. |
closable(Partial | true | | โ | The built-in close action can be present or absent but is not otherwise configurable. |
add( | Fn( | โ | Queues newest-first and returns a stable numeric key. |
update( | Fn( | โ | Replaces content in place and keeps stack position. Timeout and onClose are inherited unless set; a missing key falls back to add. |
close( | Fn( | โ | Dismisses one key and reports its onClose exactly once. |
pause_all( | Fn( | โ | Pauses every active timeout clock while keeping cards interactive. |
resume_all( | Fn( | โ | Resumes timeout countdown from its remaining duration. |
clear( | Fn( | โ | Drops every entry without emitting per-toast onClose, matching pinned queue lifecycle semantics. |
subscribe( | Fn( | โ | Returns a GPUI Subscription whose drop unsubscribes. |
variant( | ToastVariant | Color:: | Plain Toast::new now matches the default variant; semantic variants use the builder or constructors. |
is_loading( | true | | false | Replaces the indicator with a persistent spinner. |
timeout( | number | 4000 | Duration zero persists; positive durations auto-dismiss through the queue timer. |
on_close( | Fn( | โ | Reports exactly once for individual timeout, action, close-button, or programmatic dismissal, but not queue clear. |
promise( | AnyElement | โ | Shown while the future is pending as a persistent loading toast. The GPUI extension promise_task returns a task the owner can drop to cancel pending work; it leaves queue cleanup to that owner. |
Not ported10 HeroUI entries not ported
| HeroUI v3 | Why it is not ported |
|---|---|
Toast.Provider.children | The viewport renders built-in cards; queue-item render replacement is unavailable. |
Toast.Provider.className | Browser classes are unavailable; inset supplies desktop edge geometry. |
Toast.className | Browser classes are unavailable. |
Toast.Content.className | Browser classes are unavailable. |
Toast.Indicator.className | Browser classes are unavailable. |
Toast.Title.className | Browser classes are unavailable. |
Toast.Description.className | Browser classes are unavailable. |
Toast.ActionButton.className | Browser classes are unavailable. |
Toast.CloseButton.className | Browser classes are unavailable. |
ToastQueue.wrapUpdate | Browser view-transition update wrapping is unavailable. |
Parts
| Part | Description |
|---|---|
ToastViewport | Application-wide placement, visibility limit, and stacked-card owner. |
Toast | Queued notification surface and frontmost interaction owner. |
Toast | Variant glyph, caller asset/content, or loading spinner inside the shared 24px box. |
ToastPartial | Built-in title and description column. |
Toast | Required single-line semantic title. |
Toast | Optional muted description. |
ToastPartial | Frontmost small secondary action that dismisses after reporting. |
ToastPartial | Frontmost keyboard and pointer dismissal action. |
States
| State | Builder | Description |
|---|---|---|
Frontmost | ToastCardEl.frontmost | Only depth zero exposes action and close interaction; stacked cards are inert. |
Index | measured_heights | Each depth uses the measured card height and configured gap for absolute placement; collapsed depth also reuses the front card's height while its content stays clipped. |
Placement | ToastPlacement | All six top/bottom start/center/end anchors are driven. |
Hidden | hidden | Overflow stays queued and timed, mounted at zero opacity and out of flow so it does not move the frontmost close target. |
Expanded | ToastViewport::is_expanded | Hover, focus-within, or isExpanded opens the stack when more than one toast is active; measured card heights and the configured gap determine each absolute offset. |
Exiting | ToastStore::exiting | Dismissed frontmost cards stay mounted for exitDuration and translate toward the placement edge over the 350ms ease-out-fluid motion; non-frontmost cards settle through the 200ms stack scale path. Reduced motion removes the exit frame. |
Loading | Toast::is_loading | A persistent spinner replaces the indicator. |