ui.toast

ui.toast(
    *args,
    header=None,
    icon=None,
    id=None,
    type=None,
    duration_s=5,
    position='top-right',
    closable=True,
    **kwargs,
)

Create a toast notification object.

Toast notifications are temporary, non-intrusive messages that appear on screen to provide feedback to users. They support multiple semantic types, flexible positioning, auto-hide with progress bars, and optional headers with icons.

Parameters

*args : TagChild | TagAttrs = ()

Body content (HTML elements or strings).

header : Optional[str | ToastHeader | TagNode] = None

Optional header content. Can be a string (auto-converted to header), or a toast header object from toast_header.

icon : Optional[TagNode] = None

Optional icon element to display in the toast body.

id : Optional[str] = None

Optional unique identifier. Auto-generated if None.

type : Optional[ToastType] = None

Semantic type for styling. Options are "primary", "secondary", "success", "info", "warning", "danger", "error" (alias for "danger"), "light", "dark".

duration_s : Optional[int | float] = 5

Auto-hide duration in seconds. Use None or 0 to disable auto-hide.

position : str | list[str] | tuple[str, …] = 'top-right'

Screen position. Accepts "top-left", "top left", ["top", "left"], etc. Valid positions are combinations of (top/middle/bottom) × (left/center/right).

closable : bool = True

Whether to add a close button, allowing the user to close the toast. Defaults to True. When False and auto-hide is disabled, the toast cannot be dismissed by the user; use only when appropriate and be certain to close the toast programmatically with hide_toast.

****kwargs** : TagAttrValue = {}

Additional HTML attributes for the toast element.

Returns

: Toast

A toast notification object that can be passed to show_toast.

See Also

Example

See show_toast.

Examples

#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 400

## file: app.py
import time

from faicons import icon_svg

from shiny import App, Inputs, Outputs, Session, reactive, render, ui

app_ui = ui.page_fillable(
    ui.h2("Toast Notifications Demo", class_="p-3 border-bottom mb-0"),
    ui.input_dark_mode(class_="position-absolute top-0 end-0 p-3"),
    ui.layout_column_wrap(
        # Toast Builder Card
        ui.card(
            ui.card_header("Toast Builder"),
            ui.card_body(
                # Body content
                ui.input_text_area(
                    "body",
                    "Body Content",
                    value="This is a toast notification!",
                    rows=3,
                    width="100%",
                ),
                ui.layout_columns(
                    ui.div(
                        # Type
                        ui.input_select(
                            "type",
                            "Type (Background Color)",
                            choices={
                                "": "None (default)",
                                "primary": "Primary",
                                "secondary": "Secondary",
                                "success": "Success",
                                "info": "Info",
                                "warning": "Warning",
                                "danger": "Danger",
                                "light": "Light",
                                "dark": "Dark",
                            },
                            selected="",
                        ),
                        # Position
                        ui.input_select(
                            "position",
                            "Position",
                            choices={
                                "top-left": "Top Left",
                                "top-center": "Top Center",
                                "top-right": "Top Right",
                                "middle-left": "Middle Left",
                                "middle-center": "Middle Center",
                                "middle-right": "Middle Right",
                                "bottom-left": "Bottom Left",
                                "bottom-center": "Bottom Center",
                                "bottom-right": "Bottom Right",
                            },
                            selected="top-right",
                        ),
                        # Duration
                        ui.input_slider(
                            "duration",
                            "Duration (seconds, 0 = disabled)",
                            min=0,
                            max=25,
                            value=5,
                            step=1,
                            ticks=False,
                        ),
                        # Close button
                        ui.input_switch("closable", "Show Close Button", value=True),
                        ui.input_text(
                            "custom_id",
                            "Toast ID",
                            placeholder="Automatically generated",
                        ),
                    ),
                    ui.div(
                        # Header options
                        ui.input_switch("use_header", "Include Header", value=False),
                        ui.panel_conditional(
                            "input.use_header",
                            ui.input_text(
                                "header_title", "Header Title", value="Notification"
                            ),
                            ui.input_select(
                                "header_icon",
                                "Icon",
                                choices={
                                    "": "None",
                                    "circle-check": "Check",
                                    "circle-info": "Info",
                                    "triangle-exclamation": "Warning",
                                    "circle-xmark": "Error",
                                    "star": "Star",
                                    "heart": "Heart",
                                    "bell": "Bell",
                                    "user": "User",
                                    "gear": "Cog",
                                },
                                selected="",
                            ),
                            ui.input_text(
                                "header_status",
                                "Custom Status Text",
                                placeholder="'Just now', '2 mins ago'",
                            ),
                        ),
                        ui.panel_conditional(
                            "!input.use_header",
                            ui.input_select(
                                "icon_body",
                                "Icon",
                                choices={
                                    "": "None",
                                    "circle-check": "Check",
                                    "circle-info": "Info",
                                    "triangle-exclamation": "Warning",
                                    "circle-xmark": "Error",
                                    "star": "Star",
                                    "heart": "Heart",
                                    "bell": "Bell",
                                    "user": "User",
                                    "gear": "Settings",
                                    "comments": "Chat",
                                    "envelope": "Envelope",
                                    "lightbulb": "Lightbulb",
                                    "rocket": "Rocket",
                                    "shield": "Shield",
                                    "thumbs-up": "Thumbs Up",
                                    "download": "Download",
                                    "upload": "Upload",
                                    "calendar": "Calendar",
                                    "clock": "Clock",
                                    "fire": "Fire",
                                    "gift": "Gift",
                                    "trophy": "Trophy",
                                    "flag": "Flag",
                                    "thumbtack": "Pin",
                                },
                            ),
                        ),
                    ),
                ),
            ),
            ui.card_footer(
                ui.input_action_button(
                    "show_toast", "Show Toast", class_="btn-primary"
                ),
                ui.input_action_button(
                    "hide_toast", "Hide Last Toast", class_="btn-secondary"
                ),
                class_="hstack gap-2 justify-content-end",
            ),
        ),
        ui.layout_column_wrap(
            # Advanced Features
            ui.card(
                ui.card_header("Advanced Features"),
                ui.card_body(
                    ui.input_action_button(
                        "show_persistent",
                        "Show Persistent Toast",
                        class_="mb-2 w-100",
                    ),
                    ui.input_action_button(
                        "hide_persistent",
                        "Hide Persistent Toast",
                        class_="mb-2 w-100",
                    ),
                    ui.input_action_button(
                        "show_long_duration",
                        "Long Duration (10s)",
                        class_="mb-2 w-100",
                    ),
                    ui.input_action_button(
                        "show_no_close", "No Close Button", class_="mb-2 w-100"
                    ),
                    ui.input_action_button(
                        "show_custom_header",
                        "Custom Header with Icon & Status",
                        class_="mb-2 w-100",
                    ),
                ),
            ),
            # Interactive Toasts
            ui.card(
                ui.card_header("Interactive Toasts"),
                ui.card_body(
                    ui.input_action_button(
                        "show_action_buttons",
                        "Toast with Action Buttons",
                        class_="mb-2 w-100",
                    ),
                    ui.input_action_button(
                        "show_multiple", "Show Multiple Toasts", class_="mb-2 w-100"
                    ),
                    ui.input_action_button(
                        "show_all_positions", "Test All Positions", class_="mb-2 w-100"
                    ),
                    ui.input_action_button(
                        "show_dynamic_content",
                        "Toast with Dynamic Content",
                        class_="mb-2 w-100",
                    ),
                ),
            ),
            width=1,
        ),
        width=1 / 2,
        class_="bslib-page-dashboard",
        style="background: var(--bslib-dashboard-main-bg); padding: 15px; gap: 15px",
    ),
    title="Toast Notifications Demo",
    padding=0,
    gap=0,
)


def server(input: Inputs, output: Outputs, session: Session):
    # Store last toast IDs
    last_toast_id = reactive.value("")
    persistent_toast_id = reactive.value("")
    inserted_time = reactive.value(None)

    # Show toast from builder
    @reactive.effect
    @reactive.event(input.show_toast)
    def _():
        # Build header if needed
        header = None
        if input.use_header():
            header_icon = None
            if input.header_icon():
                header_icon = icon_svg(input.header_icon())

            header = ui.toast_header(
                title=input.header_title(),
                icon=header_icon,
                status=input.header_status() if input.header_status() else None,
            )

        # Build body icon if not using header
        body_icon = None
        if not input.use_header() and input.icon_body():
            body_icon = icon_svg(input.icon_body())

        # Build toast
        toast_obj = ui.toast(
            input.body(),
            header=header,
            icon=body_icon,
            id=input.custom_id() if input.custom_id() else None,
            type=input.type() if input.type() else None,
            duration_s=input.duration() if input.duration() > 0 else None,
            position=input.position(),
            closable=input.closable(),
        )

        # Show and store ID
        id = ui.show_toast(toast_obj)
        last_toast_id.set(id)

    # Hide last toast
    @reactive.effect
    @reactive.event(input.hide_toast)
    def _():
        if last_toast_id():
            ui.hide_toast(last_toast_id())
            last_toast_id.set("")

    # Advanced features
    @reactive.effect
    @reactive.event(input.show_persistent)
    def _():
        id = ui.show_toast(
            ui.toast(
                "This toast won't disappear automatically. Use the 'Hide' button to dismiss it.",
                header="Persistent Toast",
                type="info",
                duration_s=None,
            )
        )
        persistent_toast_id.set(id)

    @reactive.effect
    @reactive.event(input.hide_persistent)
    def _():
        if persistent_toast_id():
            ui.hide_toast(persistent_toast_id())
            persistent_toast_id.set("")

    @reactive.effect
    @reactive.event(input.show_long_duration)
    def _():
        ui.show_toast(
            ui.toast(
                "This toast will stay visible for 10 seconds.",
                header="Long Duration",
                type="primary",
                duration_s=10,
            )
        )

    @reactive.effect
    @reactive.event(input.show_no_close)
    def _():
        ui.show_toast(
            ui.toast(
                "This toast has no close button but will auto-hide in 3 seconds.",
                type="secondary",
                closable=False,
                duration_s=3,
            )
        )

    @reactive.effect
    @reactive.event(input.show_custom_header)
    def _():
        ui.show_toast(
            ui.toast(
                "Your profile has been updated successfully.",
                header=ui.toast_header(
                    title="Profile Updated",
                    icon=icon_svg("check"),
                    status="just now",
                ),
                type="success",
            )
        )

    # Interactive toasts
    @reactive.effect
    @reactive.event(input.show_action_buttons)
    def _():
        ui.show_toast(
            ui.toast(
                ui.p("Would you like to save your changes?"),
                ui.div(
                    ui.input_action_button(
                        "save_yes", "Save", class_="btn-sm btn-primary me-2"
                    ),
                    ui.input_action_button(
                        "save_no", "Don't Save", class_="btn-sm btn-secondary"
                    ),
                    class_="mt-2",
                ),
                id="unsaved_changes_toast",
                header="Unsaved Changes",
                type="warning",
                duration_s=None,
                closable=False,
            )
        )

    @reactive.effect
    @reactive.event(input.save_yes)
    def _():
        ui.hide_toast("unsaved_changes_toast")
        ui.show_toast(ui.toast("Saved changes", type="success"))

    @reactive.effect
    @reactive.event(input.save_no)
    def _():
        ui.hide_toast("unsaved_changes_toast")
        ui.show_toast(ui.toast("Changes were not saved", type="danger"))

    @reactive.effect
    @reactive.event(input.show_multiple)
    def _():
        ui.show_toast(ui.toast("First notification", type="primary"))
        time.sleep(0.2)
        ui.show_toast(ui.toast("Second notification", type="success"))
        time.sleep(0.2)
        ui.show_toast(ui.toast("Third notification", type="info"))

    @reactive.effect
    @reactive.event(input.show_all_positions)
    def _():
        positions = [
            "top-left",
            "top-center",
            "top-right",
            "middle-left",
            "middle-center",
            "middle-right",
            "bottom-left",
            "bottom-center",
            "bottom-right",
        ]

        types = [
            "primary",
            "success",
            "info",
            "warning",
            "danger",
            "secondary",
            "light",
            "dark",
            "primary",
        ]

        for i, pos in enumerate(positions):
            ui.show_toast(
                ui.toast(
                    f"Toast at {pos}",
                    type=types[i],
                    duration_s=4,
                    position=pos,
                )
            )

    # Dynamic content toast
    @reactive.effect
    @reactive.event(input.show_dynamic_content)
    def _():
        ui.show_toast(
            ui.toast(
                ui.div(
                    ui.p(
                        "Current time: ",
                        ui.strong(ui.output_text("toast_time", inline=True)),
                    ),
                    ui.output_plot("toast_plot", height="200px"),
                    ui.input_slider(
                        "toast_bins",
                        "Number of bins:",
                        min=5,
                        max=50,
                        value=30,
                        width="100%",
                    ),
                ),
                id="dynamic_content_toast",
                header=ui.toast_header(
                    title="Dynamic Toast",
                    status=ui.output_text("toast_status"),
                ),
                type="light",
                duration_s=None,
            )
        )
        inserted_time.set(time.time())

    @render.text
    def toast_time():
        reactive.invalidate_later(1)
        return time.strftime("%H:%M:%S")

    @render.text
    def toast_status():
        if inserted_time() is None:
            return ""

        reactive.invalidate_later(1)
        elapsed = time.time() - inserted_time()
        if elapsed < 60:
            return f"{int(elapsed)}s ago"
        else:
            return f"{int(elapsed / 60)}m ago"

    @render.plot
    def toast_plot():
        import matplotlib.pyplot as plt
        import numpy as np

        # Generate sample data (simulating faithful dataset)
        np.random.seed(42)
        eruptions = np.concatenate(
            [np.random.normal(2, 0.5, 150), np.random.normal(4.5, 0.5, 150)]
        )

        fig, ax = plt.subplots(figsize=(6, 3))
        ax.hist(
            eruptions,
            bins=input.toast_bins() if input.toast_bins() else 30,
            color="#444",
            edgecolor="none",
        )
        ax.set_title("Eruption Times")
        ax.set_xlabel("")
        ax.set_ylabel("")
        ax.set_xticks([])
        ax.set_yticks([])
        fig.patch.set_alpha(0)
        ax.patch.set_alpha(0)
        plt.tight_layout()
        return fig


app = App(app_ui, server)