Skip to content

Toast

Toasts are lightweight, non-blocking notifications. They are designed to mimic the push notifications popularized by mobile and desktop operating systems.

Bootstrap Reference

Bootstrap 5 Toasts


Quick Start

In FastStrap, we use SimpleToast for the most common case: a simple text message with a variant color.

Live Preview
SimpleToast("Success! Item added to cart.", variant="success")

Visual Examples & Use Cases

1. SimpleToast Variants

Standard colors to communicate status.

Code & Output

SimpleToast("File uploaded.", variant="info")
SimpleToast("Connection lost.", variant="danger")

2. Full Control (Standard Toast)

For rich content, headers, and custom timing, use the base Toast component.

Code & Output

Live Preview (Rich Toast)
Toast(
    "Your message has been sent.",
    title="Messenger",
    cls="shadow-sm",
    delay=5000
)

3. Toast Container

Toasts are often grouped. FastStrap handles the ToastContainer logic to ensure they stack correctly in the corner of the screen.

from faststrap import ToastContainer, add_bootstrap

# 1. Add container to your main layout
app_layout = [
    MainView(),
    ToastContainer(position="bottom-end") # Global container
]

Practical Functionality

1. Triggering Toasts via HTMX

The most common implementation is to return a Toast as part of an HTMX response (using hx-swap="beforeend" targetting the Toast Container).

@app.route("/add_item")
def add_item():
    # ... logic ...
    return SimpleToast("Item Added", variant="success") # Appends to existing list

Parameter Reference

FastStrap Param Type Bootstrap Attribute Description
title Any .toast-header Optional header element/text.
autohide bool data-bs-autohide If True, closes automatically.
delay int data-bs-delay Duration in milliseconds before closing.
duration (SimpleToast) int CSS animation delay Duration in milliseconds before fade out.
position str - Location: top-end, bottom-start, etc.
radius str \| None None Radius token (sm, md, lg, none, pill); None keeps the default rounding.
header_cls / body_cls / close_button_cls (Toast) str "" Slot class hooks for the header row, body, and dismiss button.
body_cls (SimpleToast) str "" Extra classes for the toast body.

Toast Placements

ToastContainer supports 13 placement positions via the position parameter or the ToastPlacement helper:

Position Value Screen Location Use Case
top-left Top-left corner Desktop admin tools
top-center Top center Desktop notifications
top-right Top-right corner Classic desktop notifications
bottom-left Bottom-left corner Mobile-friendly (left-handed)
bottom-center Bottom center Mobile-friendly centered
bottom-right (default) Bottom-right corner Mobile-friendly, most common
top-start Top-left (RTL-aware) RTL language support
top-end Top-right (RTL-aware) RTL language support
bottom-start Bottom-left (RTL-aware) RTL language support
bottom-end Bottom-right (RTL-aware) RTL language support
middle-start Middle-left (RTL-aware) Centered vertical alerts
middle-center Dead center Modal-like toasts
middle-end Middle-right (RTL-aware) Centered vertical alerts
from faststrap import ToastContainer, ToastPlacement

# Using a string
ToastContainer(position="bottom-right")

# Using the typed helper
ToastContainer(
    position=ToastPlacement(
        position="bottom-right",
        offset=16,   # pixels from edge
        gutter=8,    # spacing between toasts
    ),
)

The offset and gutter parameters control spacing: offset is the distance from the screen edge, and gutter is the gap between stacked toasts.

Multiple toasts in the same container stack vertically with the newest toast appearing closest to the center (configurable via stack_order).

faststrap.components.feedback.modern_toast.ToastPlacement dataclass

Source code in src/faststrap/components/feedback/modern_toast.py
@dataclass
class ToastPlacement:
    position: Literal[
        "top-left",
        "top-center",
        "top-right",
        "bottom-left",
        "bottom-center",
        "bottom-right",
        "top-start",
        "top-end",
        "bottom-start",
        "bottom-end",
        "middle-start",
        "middle-center",
        "middle-end",
    ] = "bottom-right"
    offset: int = 16
    gutter: int = 8

Multiple ToastContainers are supported: pass a distinct container_id per container, or container_id=None for an auto-generated id.

JavaScript Requirement

The Toast component requires Bootstrap's JavaScript plugin. FastStrap automatically initializes Bootstrap Toasts via faststrap-init.js when add_bootstrap(app, components=[Toast, ...]) is called, or when JS is always included.

faststrap.components.feedback.toast.SimpleToast(*children, title=None, variant=UNSET, duration=UNSET, position=UNSET, radius=None, body_cls='', **kwargs)

Simple Toast component that works without JavaScript.

Parameters:

Name Type Description Default
*children Any

Toast body content.

()
title str | None

Optional heading rendered above the body.

None
variant VariantType | None

Bootstrap alert variant color.

UNSET
duration int | None

Auto-fade delay in milliseconds.

UNSET
position ToastPositionType | None

Fixed screen position.

UNSET
radius str | None

Radius token (sm, md, lg, none); None keeps the default alert rounding.

None
body_cls str

Extra classes for the toast body.

''
**kwargs Any

Additional HTML attributes.

{}
Source code in src/faststrap/components/feedback/toast.py
@register(category="feedback")
def SimpleToast(
    *children: Any,
    title: str | None = None,
    variant: VariantType | None = UNSET,
    duration: int | None = UNSET,
    position: ToastPositionType | None = UNSET,
    radius: str | None = None,
    body_cls: str = "",
    **kwargs: Any,
) -> Div:
    """Simple Toast component that works without JavaScript.

    Args:
        *children: Toast body content.
        title: Optional heading rendered above the body.
        variant: Bootstrap alert variant color.
        duration: Auto-fade delay in milliseconds.
        position: Fixed screen position.
        radius: Radius token (``sm``, ``md``, ``lg``, ``none``); ``None``
            keeps the default alert rounding.
        body_cls: Extra classes for the toast body.
        **kwargs: Additional HTML attributes.
    """
    # Resolve API defaults
    cfg = resolve_defaults("SimpleToast", variant=variant, duration=duration, position=position)

    c_variant = cfg.get("variant", "info")
    c_duration = cfg.get("duration", 5000)
    c_position = cfg.get("position", "top-right")

    # Build base classes (no alert-dismissible — close button removed for no-JS variant)
    classes = ["alert", "fade", "show"]
    if c_variant:
        classes.append(f"alert-{c_variant}")
    radius_cls = radius_class(radius)
    if radius_cls:
        classes.append(radius_cls)

    # Position classes for fixed overlay
    position_classes = {
        "top-right": "position-fixed top-0 end-0 m-3",
        "top-left": "position-fixed top-0 start-0 m-3",
        "bottom-right": "position-fixed bottom-0 end-0 m-3",
        "bottom-left": "position-fixed bottom-0 start-0 m-3",
        "top-center": "position-fixed top-0 start-50 translate-middle-x m-3",
        "bottom-center": "position-fixed bottom-0 start-50 translate-middle-x m-3",
        "top-start": "position-fixed top-0 start-0 m-3",
        "top-end": "position-fixed top-0 end-0 m-3",
        "bottom-start": "position-fixed bottom-0 start-0 m-3",
        "bottom-end": "position-fixed bottom-0 end-0 m-3",
    }

    classes.append(position_classes.get(c_position, position_classes["top-right"]))

    # Merge with user classes
    user_cls = kwargs.pop("cls", "")
    all_classes = merge_classes(" ".join(classes), user_cls)

    # Build attributes
    attrs: dict[str, Any] = {
        "cls": all_classes,
        "role": "alert",
        "style": "z-index: 9999; max-width: 400px;",
    }

    # Add CSS for auto-hide
    if c_duration and c_duration > 0:
        duration_ms = int(c_duration)
        # Backward compatibility: historical API used seconds.
        if duration_ms <= 50:
            warnings.warn(
                "SimpleToast(duration=...) now expects milliseconds; "
                "values <= 50 are treated as seconds for compatibility.",
                DeprecationWarning,
                stacklevel=2,
            )
            duration_ms *= 1000
        duration_seconds = duration_ms / 1000
        style = f"animation: toastFadeOut 0.5s ease-in-out " f"{duration_seconds}s forwards;"
        existing_style = attrs.get("style", "")
        if existing_style:
            attrs["style"] = f"{existing_style}; {style}"
        else:
            attrs["style"] = style

    # Convert remaining kwargs
    attrs.update(convert_attrs(kwargs))

    # Build toast structure
    parts = []

    if title:
        header = Div(
            Strong(title, cls="me-auto"),
            cls="alert-heading",
        )
        parts.append(header)

    body = Div(*children, cls=merge_classes("mb-0", body_cls) if body_cls else "mb-0")
    parts.append(body)

    return Div(*parts, **attrs)

faststrap.components.feedback.toast.Toast(*children, title=None, variant=UNSET, autohide=UNSET, delay=UNSET, animation=UNSET, radius=None, header_cls='', body_cls='', close_button_cls='', **kwargs)

Bootstrap Toast component for temporary notifications.

Parameters:

Name Type Description Default
*children Any

Toast body content.

()
title str | None

Optional header text; renders the toast-header row with a dismiss button.

None
variant VariantType | None

Bootstrap color variant applied as text-bg-{variant}.

UNSET
autohide bool | None

Whether Bootstrap auto-hides the toast.

UNSET
delay int | None

Auto-hide delay in milliseconds.

UNSET
animation bool | None

Whether Bootstrap animates show/hide.

UNSET
radius str | None

Radius token (sm, md, lg, none); None keeps the default Bootstrap toast rounding.

None
header_cls str

Extra classes for the header row.

''
body_cls str

Extra classes for the body row.

''
close_button_cls str

Extra classes for the close button.

''
**kwargs Any

Additional HTML attributes.

{}
Source code in src/faststrap/components/feedback/toast.py
@register(category="feedback", requires_js=True)
def Toast(
    *children: Any,
    title: str | None = None,
    variant: VariantType | None = UNSET,
    autohide: bool | None = UNSET,
    delay: int | None = UNSET,
    animation: bool | None = UNSET,
    radius: str | None = None,
    header_cls: str = "",
    body_cls: str = "",
    close_button_cls: str = "",
    **kwargs: Any,
) -> Div:
    """Bootstrap Toast component for temporary notifications.

    Args:
        *children: Toast body content.
        title: Optional header text; renders the ``toast-header`` row with a
            dismiss button.
        variant: Bootstrap color variant applied as ``text-bg-{variant}``.
        autohide: Whether Bootstrap auto-hides the toast.
        delay: Auto-hide delay in milliseconds.
        animation: Whether Bootstrap animates show/hide.
        radius: Radius token (``sm``, ``md``, ``lg``, ``none``); ``None``
            keeps the default Bootstrap toast rounding.
        header_cls: Extra classes for the header row.
        body_cls: Extra classes for the body row.
        close_button_cls: Extra classes for the close button.
        **kwargs: Additional HTML attributes.
    """
    # Resolve API defaults
    cfg = resolve_defaults(
        "Toast", variant=variant, autohide=autohide, delay=delay, animation=animation
    )

    c_variant = cfg.get("variant")
    c_autohide = cfg.get("autohide", True)
    c_delay = cfg.get("delay", 5000)
    c_animation = cfg.get("animation", True)

    # Build base classes
    classes = ["toast"]
    if c_variant:
        classes.append(f"text-bg-{c_variant}")
    radius_cls = radius_class(radius)
    if radius_cls:
        classes.append(radius_cls)

    # Merge with user classes
    user_cls = kwargs.pop("cls", "")
    all_classes = merge_classes(" ".join(classes), user_cls)

    # Build attributes
    attrs: dict[str, Any] = {
        "cls": all_classes,
        "role": "alert",
        "aria-live": "assertive",
        "aria-atomic": "true",
    }

    # Bootstrap toast data attributes
    if c_autohide:
        attrs["data-bs-autohide"] = "true"
        attrs["data-bs-delay"] = str(c_delay)
    else:
        attrs["data-bs-autohide"] = "false"

    if c_animation:
        attrs["data-bs-animation"] = "true"

    # Convert remaining kwargs
    attrs.update(convert_attrs(kwargs))

    # Build toast structure
    parts = []

    if title:
        # Bootstrap's default close glyph is black; use the white variant on
        # dark `text-bg-*` fills so it stays visible.
        dark_variants = {"primary", "secondary", "success", "danger", "dark"}
        close_cls = "btn-close"
        if c_variant in dark_variants:
            close_cls = merge_classes(close_cls, "btn-close-white")
        header = Div(
            Strong(title, cls="me-auto"),
            Button(
                type="button",
                cls=merge_classes(close_cls, close_button_cls),
                data_bs_dismiss="toast",
                aria_label="Close",
            ),
            cls=merge_classes("toast-header", header_cls),
        )
        parts.append(header)

    body = Div(*children, cls=merge_classes("toast-body", body_cls))
    parts.append(body)

    return Div(*parts, **attrs)

faststrap.components.feedback.toast.ToastContainer(*toasts, position=UNSET, container_id='toast-container', **kwargs)

Container for positioning toasts on the page.

Parameters:

Name Type Description Default
*toasts Any

Toast components to place inside the container.

()
position ToastPositionType | None

Fixed screen position of the container.

UNSET
container_id str | None

DOM id for HTMX targeting; pass None to auto-generate a unique id when multiple containers are needed.

'toast-container'
**kwargs Any

Additional HTML attributes.

{}
Source code in src/faststrap/components/feedback/toast.py
@register(category="feedback")
def ToastContainer(
    *toasts: Any,
    position: ToastPositionType | None = UNSET,
    container_id: str | None = "toast-container",
    **kwargs: Any,
) -> Div:
    """Container for positioning toasts on the page.

    Args:
        *toasts: Toast components to place inside the container.
        position: Fixed screen position of the container.
        container_id: DOM id for HTMX targeting; pass ``None`` to auto-generate
            a unique id when multiple containers are needed.
        **kwargs: Additional HTML attributes.
    """
    # Resolve API defaults
    cfg = resolve_defaults("ToastContainer", position=position)
    c_position = cfg.get("position", "top-end")

    # Build position classes
    classes = ["toast-container", "position-fixed", "p-3"]

    position_map = {
        "top-start": "top-0 start-0",
        "top-center": "top-0 start-50 translate-middle-x",
        "top-end": "top-0 end-0",
        "middle-start": "top-50 start-0 translate-middle-y",
        "middle-center": "top-50 start-50 translate-middle",
        "middle-end": "top-50 end-0 translate-middle-y",
        "bottom-start": "bottom-0 start-0",
        "bottom-center": "bottom-0 start-50 translate-middle-x",
        "bottom-end": "bottom-0 end-0",
    }

    classes.append(position_map.get(c_position, position_map["top-end"]))

    # Merge with user classes
    user_cls = kwargs.pop("cls", "")
    all_classes = merge_classes(classes, user_cls)

    # Build attributes
    explicit_id = kwargs.pop("id", None)
    if explicit_id is not None and container_id is not None and explicit_id != container_id:
        raise ValueError(
            "ToastContainer received both container_id and id with different values; "
            "use container_id only."
        )
    resolved_id = explicit_id or container_id
    if resolved_id is None:
        # Auto-generate a unique id so multiple containers can coexist.
        import uuid

        resolved_id = f"toast-container-{uuid.uuid4().hex[:8]}"
    attrs: dict[str, Any] = {"cls": all_classes, "id": resolved_id}
    attrs.update(convert_attrs(kwargs))

    return Div(*toasts, **attrs)