express.ui.toast

express.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 reactive
from shiny.express import input, ui

ui.page_opts(
    title=ui.tags.h2("Toast Notifications Demo", class_="h3 ps-3 pt-3 mb-0"),
    fillable=True,
    padding=0,
)

ui.input_dark_mode(class_="position-absolute top-0 end-0 p-3")

with ui.layout_column_wrap(
    width=1 / 2,
    class_="bslib-page-dashboard",
    style="background: var(--bslib-dashboard-main-bg); padding: 15px; gap: 15px",
):
    # Toast Builder Card
    with ui.card():
        ui.card_header("Toast Builder")

        with ui.card_body():
            ui.input_text_area(
                "body",
                "Body Content",
                value="This is a toast notification!",
                rows=3,
                width="100%",
            )

            with ui.layout_columns():
                with ui.div():
                    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="",
                    )

                    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",
                    )

                    ui.input_slider(
                        "duration",
                        "Duration (seconds, 0 = disabled)",
                        min=0,
                        max=25,
                        value=5,
                        step=1,
                        ticks=False,
                    )

                    ui.input_switch("closable", "Show Close Button", value=True)

                with ui.div():
                    ui.input_switch("use_header", "Include Header", value=False)

                    with 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="",
                        )

                    with 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",
                            },
                        )

        with ui.card_footer(class_="hstack gap-2 justify-content-end"):
            ui.input_action_button("show_toast", "Show Toast", class_="btn-primary")
            ui.input_action_button(
                "hide_toast", "Hide Last Toast", class_="btn-secondary"
            )

    with ui.layout_column_wrap(width=1):
        # Advanced Features
        with ui.card():
            ui.card_header("Advanced Features")
            with 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
        with ui.card():
            ui.card_header("Interactive Toasts")
            with 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"
                )

# Store last toast IDs
last_toast_id = reactive.value("")
persistent_toast_id = reactive.value("")


# 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=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,
        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,
            )
        )