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.

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

Simple Toast component that works without JavaScript.

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,
    **kwargs: Any,
) -> Div:
    """Simple Toast component that works without JavaScript."""
    # 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
    classes = ["alert", "alert-dismissible", "fade", "show"]
    if c_variant:
        classes.append(f"alert-{c_variant}")

    # 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 {duration_seconds}s 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"),
            Button(
                type="button",
                cls="btn-close",
                aria_label="Close",
            ),
            cls="alert-heading",
        )
        parts.append(header)

    body = Div(*children, cls="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, **kwargs)

Bootstrap Toast component for temporary notifications.

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,
    **kwargs: Any,
) -> Div:
    """Bootstrap Toast component for temporary notifications."""
    # 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}")

    # 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:
        header = Div(
            Strong(title, cls="me-auto"),
            Button(
                type="button",
                cls="btn-close",
                data_bs_dismiss="toast",
                aria_label="Close",
            ),
            cls="toast-header",
        )
        parts.append(header)

    body = Div(*children, cls="toast-body")
    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.

Source code in src/faststrap/components/feedback/toast.py
@register(category="feedback")
def ToastContainer(
    *toasts: Any,
    position: ToastPositionType | None = UNSET,
    container_id: str = "toast-container",
    **kwargs: Any,
) -> Div:
    """Container for positioning toasts on the page."""
    # 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(" ".join(classes), user_cls)

    # Build attributes
    explicit_id = kwargs.pop("id", None)
    if explicit_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."
        )
    attrs: dict[str, Any] = {"cls": all_classes, "id": container_id}
    attrs.update(convert_attrs(kwargs))

    return Div(*toasts, **attrs)