Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Toast

Live Demo

Open the demo in a separate tab.

Toast supports the same fluent .header(...), .body(...), and .footer(...) section helpers as Card. The standalone footer(...) function creates the separate Footer component.

Use UiElement::toast(...) to compose top, middle, and bottom regions:

#![allow(unused)]
fn main() {
UiElement::toast(Toast::success("Changes saved").title("Saved"))
    .header(text("Account update"))
    .body(text("All devices will receive the update."))
    .footer(button().text("View account"))
}

Each section accepts one element or an iterable, and repeated calls append content. Header, body, and footer render vertically in that order regardless of builder-call order. The header appears above the notification row; custom body content follows the built-in title/message within that row; footer content appears beneath it, not beside it. The icon, dismiss control, duration, positioning, and notification lifecycle remain intact.

A Toast is a transient notification presented as a floating Card.

It is a composite component built from familiar Beverly primitives: a Card, content, a close Button, positioning, animation, and a timed lifecycle.

A Toast appears with an entrance animation, remains visible for a configurable period, and then fades away.

It can also be dismissed immediately by the user.

A Toast is a Card with a lifecycle.

Basic Usage

Create a Toast with toast() and choose its type:

#![allow(unused)]
fn main() {
toast(ToastType::Success)
    .children([
        text(localize("user.saved")),
    ])
}

Other predefined types follow the same semantic vocabulary as Alert:

#![allow(unused)]
fn main() {
toast(ToastType::Info)
    .children([
        text(localize("user.updated")),
    ])
}
#![allow(unused)]
fn main() {
toast(ToastType::Warning)
    .children([
        text(localize("user.unsaved_changes")),
    ])
}
#![allow(unused)]
fn main() {
toast(ToastType::Error)
    .children([
        text(localize("user.save_failed")),
    ])
}

The type determines the Toast’s presentation.

It does not create application state.

The type describes the presentation. The application owns the meaning.

Toast vs Alert

Toast and Alert share a semantic vocabulary, but they serve different presentation models.

AlertToast
Persistent/in-contextTransient/floating
Remains visibleAutomatically dismisses
Communicates within the layoutAppears above the layout
Primarily contextualPrimarily immediate feedback
No inherent entrance lifecycleEnters and exits with animation
Card presentationComposite animated Card

An Alert might communicate:

#![allow(unused)]
fn main() {
alert(AlertType::Error)
    .children([
        text(localize("user.save_failed")),
    ])
}

A Toast communicates the same kind of information differently:

#![allow(unused)]
fn main() {
toast(ToastType::Error)
    .children([
        text(localize("user.save_failed")),
    ])
}

The semantic type is similar. The presentation and lifecycle are different.

Alert communicates in context. Toast communicates temporarily above the interface.

Content and Named Sections

children() is the general composition primitive.

#![allow(unused)]
fn main() {
toast(ToastType::Success)
    .children([
        text(localize("user.saved")),
        text(localize("user.saved_description")),
    ])
}

For more structured content, Toast provides three named sections:

  • header
  • body
  • footer

These sections are semantic conveniences, not a replacement for children().

A simple Toast can use children():

#![allow(unused)]
fn main() {
toast(ToastType::Info)
    .children(
        text(localize("user.profile_updated"))
    )
}

A structured Toast can use named sections:

#![allow(unused)]
fn main() {
toast(ToastType::Success)
    .header(
        text(localize("user.saved"))
    )
    .body(
        text(localize("user.saved_description"))
    )
    .footer(
        text(localize("user.saved_time"))
    )
}

Each named section can accept one child or many children:

#![allow(unused)]
fn main() {
toast(ToastType::Success)
    .header([
        icon("check"),
        text(localize("user.saved")),
    ])
}
#![allow(unused)]
fn main() {
toast(ToastType::Success)
    .body([
        text(localize("user.saved")),
        text(localize("user.saved_description")),
    ])
}
#![allow(unused)]
fn main() {
toast(ToastType::Success)
    .footer([
        text(localize("user.saved_time")),
        text(localize("user.saved_location")),
    ])
}

The named sections provide useful structure without introducing another composition model.

children() is the general primitive. header, body, and footer add useful semantics.

The Header is useful for a short title or primary message.

#![allow(unused)]
fn main() {
toast(ToastType::Error)
    .header(
        text(localize("user.save_failed"))
    )
}

It can contain multiple components:

#![allow(unused)]
fn main() {
toast(ToastType::Error)
    .header([
        icon("error"),
        text(localize("user.save_failed")),
    ])
}

The Header does not own the error state. It simply presents content.

Body

The Body is useful for supporting information.

#![allow(unused)]
fn main() {
toast(ToastType::Error)
    .body(
        text(localize("user.save_failed_description"))
    )
}

It can contain arbitrary components:

#![allow(unused)]
fn main() {
toast(ToastType::Error)
    .body([
        text(localize("user.save_failed_description")),
        text(localize("user.retry_available")),
    ])
}

The Toast does not interpret the content.

The application provides the meaning. The Toast provides the presentation.

The Footer is useful for secondary actions or supporting information.

#![allow(unused)]
fn main() {
toast(ToastType::Info)
    .footer([
        button().text(localize("user.undo"))
            .on("click", Command::User::UndoDelete),
    ])
}

The Footer can contain multiple components:

#![allow(unused)]
fn main() {
toast(ToastType::Info)
    .footer([
        text(localize("user.deleted")),
        button().text(localize("user.undo"))
            .on("click", Command::User::UndoDelete),
    ])
}

The Button remains an ordinary Beverly Button. The Toast does not redefine its behavior.

Close Button

A Toast includes a close control so the user can dismiss it before its timer expires.

The close control is part of the Toast’s presentation rather than application content.

Its accessible name should clearly communicate its purpose:

#![allow(unused)]
fn main() {
toast(ToastType::Info)
    .close_label(localize("toast.dismiss"))
    .children([
        text(localize("user.profile_updated")),
    ])
}

The user can therefore either wait for the Toast to expire or dismiss it immediately.

The application does not need to construct a separate close-button architecture for every Toast.

The Toast manages its presentation lifecycle. The application decides when the Toast exists.

Position

A Toast can be positioned at the top or bottom of the interface and aligned left, center, or right.

#![allow(unused)]
fn main() {
toast(ToastType::Success)
    .position(ToastPosition::TopRight)
    .children([
        text(localize("user.saved")),
    ])
}

Other positions include:

#![allow(unused)]
fn main() {
toast(ToastType::Info)
    .position(ToastPosition::TopLeft)
}
#![allow(unused)]
fn main() {
toast(ToastType::Info)
    .position(ToastPosition::TopCenter)
}
#![allow(unused)]
fn main() {
toast(ToastType::Info)
    .position(ToastPosition::BottomLeft)
}
#![allow(unused)]
fn main() {
toast(ToastType::Info)
    .position(ToastPosition::BottomCenter)
}
#![allow(unused)]
fn main() {
toast(ToastType::Info)
    .position(ToastPosition::BottomRight)
}

The position is a property of the Toast’s presentation, not its application meaning.

Same Toast. Different place in the interface.

Duration

A Toast remains visible for a configurable period before beginning its exit animation.

#![allow(unused)]
fn main() {
toast(ToastType::Success)
    .duration(5000)
    .children([
        text(localize("user.saved")),
    ])
}

The duration is expressed in milliseconds.

A longer duration can be used when the content requires more time to read:

#![allow(unused)]
fn main() {
toast(ToastType::Info)
    .duration(8000)
    .children([
        text(localize("user.import_complete")),
    ])
}

The duration belongs to the Toast’s presentation lifecycle, not the application’s Model.

Entrance and Exit Animation

Animation is a fundamental part of the Toast primitive.

When the Toast appears, it enters the interface using its entrance animation.

When its duration expires, it exits before being removed.

Conceptually:

Create
  ↓
Entering
  ↓
Visible
  ↓
Timer / Dismiss
  ↓
Exiting
  ↓
Removed

The animation can naturally reflect the Toast’s position.

A Toast entering from the bottom can move upward into place, while a Toast entering from the top can move downward.

The exact animation remains an implementation detail of the Toast primitive.

The Toast owns the animation. The application does not have to orchestrate it.

Lifecycle

The Toast has presentation state internally:

  • opening
  • visible
  • closing
  • closed

The application does not need to manage these states manually.

This is UI state, not application state.

The Model continues to own application state while the Toast manages its own temporary presentation lifecycle.

Presentation state belongs to the component. Application state belongs to the Model.

Application Events

A Toast can be triggered by an application Event:

#![allow(unused)]
fn main() {
controller! {
    User::Saved => [
        Toast::Success,
    ],
}
}

The Event describes what happened. The Toast determines how that fact is presented.

The Toast does not need to know why the application produced it.

Events describe facts. Toast describes presentation.

Data-Driven Toasts

Toast content can come directly from application data:

#![allow(unused)]
fn main() {
toast(notification.toast_type())
    .children([
        text(notification.message()),
    ])
}

An application can also map its own domain types to Toast presentation types:

#![allow(unused)]
fn main() {
enum DeploymentStatus {
    Running,
    Failed,
    RolledBack,
}
}

Then ordinary Rust determines how those domain states should be presented:

#![allow(unused)]
fn main() {
fn toast_type(status: DeploymentStatus) -> ToastType {
    match status {
        DeploymentStatus::Running => ToastType::Success,
        DeploymentStatus::Failed => ToastType::Error,
        DeploymentStatus::RolledBack => ToastType::Warning,
    }
}
}

The framework provides useful defaults without defining the application’s domain vocabulary.

Your data, your type system.

Higher-Level Toasts

Building a higher-level Toast is just an ordinary Rust function that returns a Toast.

#![allow(unused)]
fn main() {
fn saved_toast() -> Toast {
    toast(ToastType::Success)
        .header(
            text(localize("user.saved"))
        )
        .body(
            text(localize("user.saved_description"))
        )
}
}

Use it like any other component:

#![allow(unused)]
fn main() {
saved_toast()
}

There is no nested Toast component or special extension system.

Higher-level components are ordinary Rust functions that return lower-level components.

Accessibility

A Toast communicates information that may appear outside the user’s current reading context, so its announcement behavior is part of the component’s implementation.

The close control must be keyboard accessible and have an accessible name.

#![allow(unused)]
fn main() {
toast(ToastType::Success)
    .close_label(localize("toast.dismiss"))
    .children([
        text(localize("user.saved")),
    ])
}

The Toast should not depend on color or animation alone to communicate meaning.

The semantic type and accessible announcement provide the meaning; visual treatment reinforces it.

Accessible by construction, not accessible by cleanup.

What Toast Does Not Do

Toast does not:

  • own application state
  • replace the Model
  • become a notification database
  • manage application business logic
  • define domain types
  • replace Alert
  • replace Modal
  • require a separate state-management system

It owns its presentation lifecycle:

  • positioning
  • entrance animation
  • visibility duration
  • exit animation
  • dismissal
  • close-button presentation
  • content composition

The application owns the reason the Toast exists.

Alert, Toast, and Modal

These components can share underlying primitives while serving different purposes.

ComponentPurpose
alert(type)Persistent communication within the interface
toast(type)Transient floating feedback
modal()Temporary surface requiring focused interaction

The distinction is primarily lifecycle and presentation.

Alert communicates. Toast appears and fades. Modal interrupts.

Design Principle

Toast is deliberately a composite component rather than a new UI system.

Conceptually, it combines:

Toast
├── Card
├── Header / Body / Footer
├── Close Button
├── Position
├── Entrance Animation
├── Duration
└── Exit Animation

Each piece already exists conceptually in Beverly.

Toast simply combines them into a useful, reusable pattern.

The Toast is a specialized Card with a lifecycle.

Create it with a semantic type. Configure its position and duration. Compose its content with children() or header, body, and footer. Let Beverly manage the presentation lifecycle.

Create. Configure. Compose. Animate. Dismiss.