Skip to content

Error Dialog

The ErrorDialog component displays error messages in a Bootstrap modal, perfect for inline errors that don't require a full page reload. It supports retry actions, HTMX out-of-band swaps, and backend error messages.

Goal

By the end of this guide, you'll be able to show beautiful error modals for AJAX failures, validation errors, and backend exceptions without writing any JavaScript.


Quick Start

Here's the simplest way to show an error dialog.

Live Preview
ErrorDialog(message="Something went wrong. Please try again.")

Visual Examples & Use Cases

1. Variants

Different severity levels for different errors.

Danger (Default)
Error

Failed to save changes

ErrorDialog(
    message="Failed to save changes",
    variant="danger"
)
Warning
Warning

Your session will expire in 5 minutes

ErrorDialog(
    message="Your session will expire in 5 minutes",
    title="Warning",
    variant="warning"
)
Info
Notice

This feature is currently in beta

ErrorDialog(
    message="This feature is currently in beta",
    title="Notice",
    variant="info"
)

2. With Retry Action

Let users retry failed operations.

ErrorDialog(
    message="Network timeout. Please try again.",
    retry_url="/api/save",
    retry_text="Retry",
    modal_id="network-error"
)

Practical Functionality

HTMX Integration

Show errors from HTMX requests.

@app.post("/profile/save")
def save_profile(req):
    try:
        update_profile(req.form)
        return Div("Profile saved!", cls="alert alert-success")
    except ValidationError as e:
        # Return error dialog via HTMX
        return ErrorDialog(
            message=str(e),
            title="Validation Error",
            variant="warning"
        )
    except Exception as e:
        return ErrorDialog(
            message="Failed to save profile. Please try again.",
            retry_url="/profile/save",
            retry_text="Try Again"
        )

Out-of-Band Swaps

Show error dialog while updating other content.

@app.post("/cart/add")
def add_to_cart(product_id: int):
    try:
        cart.add(product_id)

        # Return updated cart + success message
        return Div(
            CartWidget(cart),
            Toast("Added to cart!", variant="success", hx_swap_oob="true")
        )
    except OutOfStockError:
        # Return cart + error dialog
        return Div(
            CartWidget(cart),
            ErrorDialog(
                message="This item is out of stock",
                modal_id="stock-error",
                hx_swap_oob="true",
                show=True  # Show immediately
            )
        )

Backend Error Messages

Display backend exceptions to users.

@app.post("/api/process")
def process_data(req):
    try:
        result = expensive_operation(req.form)
        return result
    except DatabaseError as e:
        # Show technical error in development
        if app.debug:
            return ErrorDialog(
                message=f"Database error: {e}",
                title="Database Error"
            )
        else:
            # Show user-friendly error in production
            return ErrorDialog(
                message="Unable to process your request. Our team has been notified.",
                retry_url="/api/process"
            )

Integration Patterns

With Form Validation

@app.post("/register")
def register(req):
    email = req.form.get("email")
    password = req.form.get("password")

    # Validate
    if not email or "@" not in email:
        return ErrorDialog(
            message="Please enter a valid email address",
            title="Validation Error",
            variant="warning",
            modal_id="validation-error",
            show=True
        )

    if len(password) < 8:
        return ErrorDialog(
            message="Password must be at least 8 characters",
            title="Validation Error",
            variant="warning",
            show=True
        )

    # Create user
    create_user(email, password)
    return hx_redirect("/dashboard")

With Confirmation Dialogs

Combine with retry for dangerous actions.

@app.delete("/account")
def delete_account(req):
    user = req.session.get("user")

    try:
        delete_user(user.id)
        return hx_redirect("/goodbye")
    except Exception as e:
        return ErrorDialog(
            message="Failed to delete account. Please contact support if this persists.",
            title="Deletion Failed",
            retry_url="/account/delete",
            retry_text="Try Again"
        )

Auto-Show on Load

Show error immediately when page loads.

@app.get("/checkout")
def checkout(req):
    cart = get_cart(req)

    if cart.is_empty():
        # Show error dialog on page load
        return Container(
            H1("Checkout"),
            ErrorDialog(
                message="Your cart is empty",
                title="Cannot Checkout",
                variant="warning",
                show=True,  # Auto-show
                modal_id="empty-cart"
            )
        )

    return CheckoutPage(cart)

Parameter Reference

Parameter Type Default Description
message str Required Error message to display
title str "Error" Modal title
variant str "danger" Color variant (danger, warning, info)
modal_id str "error-dialog" Unique modal ID
retry_url str \| None None URL for retry button (None to hide)
retry_text str "Retry" Text for retry button
show bool False Whether to show modal immediately
**kwargs Any - Additional HTML attributes

Best Practices

✅ Do This

# Use appropriate variants
ErrorDialog(message="Invalid input", variant="warning")
ErrorDialog(message="Server error", variant="danger")
ErrorDialog(message="Feature unavailable", variant="info")

# Provide retry for transient errors
ErrorDialog(
    message="Network timeout",
    retry_url="/api/save",
    retry_text="Try Again"
)

# Use unique IDs for multiple dialogs
ErrorDialog(message="Error 1", modal_id="error-1")
ErrorDialog(message="Error 2", modal_id="error-2")

❌ Don't Do This

# Don't expose sensitive errors in production
ErrorDialog(message=str(database_connection_string))

# Don't use wrong variants
ErrorDialog(message="Success!", variant="danger")  # Use Toast instead

# Don't reuse modal IDs
ErrorDialog(message="Error 1", modal_id="error")
ErrorDialog(message="Error 2", modal_id="error")  # Conflict!

faststrap.components.feedback.error_dialog.ErrorDialog(message, title='Error', variant=None, modal_id='error-dialog', retry_url=None, retry_text='Retry', close_text='Close', show=True, **kwargs)

Modal/dialog variant for inline error display.

Shows error messages from backend or client-side validation in a modal dialog. Supports retry actions and custom styling.

Parameters:

Name Type Description Default
message str

Error message to display (can be from backend)

required
title str

Dialog title

'Error'
variant VariantType | None

Bootstrap variant for styling (danger, warning, info)

None
modal_id str

Unique ID for the modal

'error-dialog'
retry_url str | None

Optional URL to retry the failed action (shows retry button)

None
retry_text str

Text for retry button

'Retry'
close_text str

Text for close button

'Close'
show bool

Whether to show modal immediately

True
**kwargs Any

Additional HTML attributes

{}

Returns:

Type Description
Any

Modal component with error content

Example

Basic error dialog:

ErrorDialog(message="Failed to save record")

Backend error with retry:

ErrorDialog( ... message="Network timeout. Please try again.", ... retry_url="/api/save", ... retry_text="Try Again" ... )

Custom variant and title:

ErrorDialog( ... message="This action requires premium access", ... title="Access Restricted", ... variant="warning", ... retry_url="/pricing", ... retry_text="Upgrade Now" ... )

HTMX integration (show on error):

@app.post("/save") def save(req): try: # ... save logic ... return Card("Success!") except Exception as e: return ErrorDialog( message=str(e), modal_id="save-error", hx_swap_oob="true" )

Note

For full-page errors, use ErrorPage instead.

The dialog can be triggered via HTMX out-of-band swaps:

# Server returns error dialog alongside main content
return (main_content, ErrorDialog(message="Error", hx_swap_oob="true"))

Or shown programmatically with Bootstrap's modal API:

new bootstrap.Modal(document.getElementById('error-dialog')).show();

Source code in src/faststrap/components/feedback/error_dialog.py
@register(category="feedback", requires_js=True)
def ErrorDialog(
    message: str,
    title: str = "Error",
    variant: VariantType | None = None,
    modal_id: str = "error-dialog",
    retry_url: str | None = None,
    retry_text: str = "Retry",
    close_text: str = "Close",
    show: bool = True,
    **kwargs: Any,
) -> Any:
    """Modal/dialog variant for inline error display.

    Shows error messages from backend or client-side validation in a modal dialog.
    Supports retry actions and custom styling.

    Args:
        message: Error message to display (can be from backend)
        title: Dialog title
        variant: Bootstrap variant for styling (danger, warning, info)
        modal_id: Unique ID for the modal
        retry_url: Optional URL to retry the failed action (shows retry button)
        retry_text: Text for retry button
        close_text: Text for close button
        show: Whether to show modal immediately
        **kwargs: Additional HTML attributes

    Returns:
        Modal component with error content

    Example:
        Basic error dialog:
        >>> ErrorDialog(message="Failed to save record")

        Backend error with retry:
        >>> ErrorDialog(
        ...     message="Network timeout. Please try again.",
        ...     retry_url="/api/save",
        ...     retry_text="Try Again"
        ... )

        Custom variant and title:
        >>> ErrorDialog(
        ...     message="This action requires premium access",
        ...     title="Access Restricted",
        ...     variant="warning",
        ...     retry_url="/pricing",
        ...     retry_text="Upgrade Now"
        ... )

        HTMX integration (show on error):
        >>> @app.post("/save")
        >>> def save(req):
        >>>     try:
        >>>         # ... save logic ...
        >>>         return Card("Success!")
        >>>     except Exception as e:
        >>>         return ErrorDialog(
        >>>             message=str(e),
        >>>             modal_id="save-error",
        >>>             hx_swap_oob="true"
        >>>         )

    Note:
        For full-page errors, use `ErrorPage` instead.

        The dialog can be triggered via HTMX out-of-band swaps:
        ```python
        # Server returns error dialog alongside main content
        return (main_content, ErrorDialog(message="Error", hx_swap_oob="true"))
        ```

        Or shown programmatically with Bootstrap's modal API:
        ```javascript
        new bootstrap.Modal(document.getElementById('error-dialog')).show();
        ```
    """
    # Resolve API defaults
    from ...core.theme import resolve_defaults

    cfg = resolve_defaults("ErrorDialog", variant=variant)
    c_variant = cfg.get("variant", "danger")

    # Build icon based on variant
    icon_map = {
        "danger": "x-circle-fill",
        "warning": "exclamation-triangle-fill",
        "info": "info-circle-fill",
        "success": "check-circle-fill",
    }
    icon_name = icon_map.get(c_variant, "exclamation-circle-fill")

    # Build error content
    error_icon = Div(
        Icon(icon_name),
        cls=f"text-{c_variant} fs-1 mb-3",
    )

    error_message = P(
        message,
        cls="mb-0",
    )

    error_content = Div(
        error_icon,
        error_message,
        cls="text-center py-3",
    )

    # Build footer buttons
    footer_buttons = []

    if retry_url:
        retry_btn = Button(
            retry_text,
            variant=c_variant,
            hx_get=retry_url,
            hx_target=f"#{modal_id}",
            hx_swap="outerHTML",
            data_bs_dismiss="modal",
        )
        footer_buttons.append(retry_btn)

    close_btn = Button(
        close_text,
        variant="secondary" if retry_url else c_variant,
        data_bs_dismiss="modal",
    )
    footer_buttons.append(close_btn)

    # Build modal
    modal = Modal(
        error_content,
        modal_id=modal_id,
        title=title,
        footer=Div(*footer_buttons, cls="d-flex gap-2 justify-content-end w-100"),
        centered=True,
        **kwargs,
    )

    # Auto-show if requested
    if show:
        # Add script to show modal on load
        from fasthtml.common import Script

        show_script = Script(f"new bootstrap.Modal(document.getElementById('{modal_id}')).show();")
        return (modal, show_script)

    return modal