Skip to content

Theme Toggle

The ThemeToggle component creates a dark/light mode switch with HTMX server-side persistence.

Quick Start

ThemeToggle(current_theme="light", show_label=True)

Usage Scenarios

With Optional Icon

ThemeToggle(current_theme="dark", show_icon=True)

With Label

ThemeToggle(current_theme="dark", show_label=True, label_text="Dark Mode")

In Navbar

Navbar(
    brand="MyApp",
    items=[
        NavItem("Home", href="/"),
        ThemeToggle(current_theme=req.session.get("theme", "light"), cls="ms-auto")
    ]
)

Server-Side Theme Management

@app.post("/theme/toggle")
def toggle_theme(req):
    current = req.session.get("theme", "light")
    new_theme = "dark" if current == "light" else "light"
    req.session["theme"] = new_theme
    from faststrap.presets import hx_refresh
    return hx_refresh()

Parameter Reference

Parameter Type Default Description
current_theme str "auto" Current theme ("light", "dark", "auto")
endpoint str "/theme/toggle" Server endpoint for theme changes
toggle_id str "theme-toggle" Unique ID for the toggle
show_label bool False Whether to show label text
label_text str "Dark Mode" Label text to display
show_icon bool False Whether to show the decorative sun/moon icon
**kwargs Any - Additional HTML attributes

Accessibility

  • Uses role="switch" on the input for proper screen reader semantics.
  • Label is associated via for and id attributes.
  • Decorative icons use aria-hidden="true".

API Reference

faststrap.components.forms.theme_toggle.ThemeToggle(current_theme='auto', endpoint='/theme/toggle', toggle_id='theme-toggle', show_label=False, label_text='Dark Mode', show_icon=False, **kwargs)

Dark/light mode toggle switch with HTMX persistence.

Creates a toggle switch that POSTs to a server endpoint to persist theme preference. The server should handle the toggle logic and return updated UI or trigger a page refresh.

Parameters:

Name Type Description Default
current_theme ThemeType

Current theme ("light", "dark", "auto")

'auto'
endpoint str

Server endpoint to POST theme changes

'/theme/toggle'
toggle_id str

Unique ID for the toggle

'theme-toggle'
show_label bool

Whether to show label text

False
label_text str

Label text to display

'Dark Mode'
show_icon bool

Whether to render the decorative theme icon

False
**kwargs Any

Additional HTML attributes

{}

Returns:

Type Description
Div

Div containing the theme toggle switch

Examples:

Basic toggle:

>>> ThemeToggle()

With label:

>>> ThemeToggle(
...     current_theme="dark",
...     show_label=True,
...     label_text="Dark Mode"
... )

Custom endpoint:

>>> ThemeToggle(
...     endpoint="/api/user/theme",
...     toggle_id="user-theme-toggle"
... )

Server-side handler:

@app.post("/theme/toggle")
def toggle_theme(req: Request):
    current = req.session.get("theme", "light")
    new_theme = "dark" if current == "light" else "light"
    req.session["theme"] = new_theme

    # Return updated UI or trigger refresh
    from faststrap.presets import hx_refresh
    return hx_refresh()

Note

The toggle uses Bootstrap's form-check-input styling. The server endpoint should: 1. Read current theme from session/cookie 2. Toggle to new theme 3. Save to session/cookie 4. Return response (refresh, redirect, or updated HTML)

Source code in src/faststrap/components/forms/theme_toggle.py
@register(category="forms")
def ThemeToggle(
    current_theme: ThemeType = "auto",
    endpoint: str = "/theme/toggle",
    toggle_id: str = "theme-toggle",
    show_label: bool = False,
    label_text: str = "Dark Mode",
    show_icon: bool = False,
    **kwargs: Any,
) -> Div:
    """Dark/light mode toggle switch with HTMX persistence.

    Creates a toggle switch that POSTs to a server endpoint to persist
    theme preference. The server should handle the toggle logic and return
    updated UI or trigger a page refresh.

    Args:
        current_theme: Current theme ("light", "dark", "auto")
        endpoint: Server endpoint to POST theme changes
        toggle_id: Unique ID for the toggle
        show_label: Whether to show label text
        label_text: Label text to display
        show_icon: Whether to render the decorative theme icon
        **kwargs: Additional HTML attributes

    Returns:
        Div containing the theme toggle switch

    Examples:
        Basic toggle:
        >>> ThemeToggle()

        With label:
        >>> ThemeToggle(
        ...     current_theme="dark",
        ...     show_label=True,
        ...     label_text="Dark Mode"
        ... )

        Custom endpoint:
        >>> ThemeToggle(
        ...     endpoint="/api/user/theme",
        ...     toggle_id="user-theme-toggle"
        ... )

        Server-side handler:
        ```python
        @app.post("/theme/toggle")
        def toggle_theme(req: Request):
            current = req.session.get("theme", "light")
            new_theme = "dark" if current == "light" else "light"
            req.session["theme"] = new_theme

            # Return updated UI or trigger refresh
            from faststrap.presets import hx_refresh
            return hx_refresh()
        ```

    Note:
        The toggle uses Bootstrap's form-check-input styling.
        The server endpoint should:
        1. Read current theme from session/cookie
        2. Toggle to new theme
        3. Save to session/cookie
        4. Return response (refresh, redirect, or updated HTML)
    """
    # Determine if checked
    is_checked = current_theme == "dark"

    # Build toggle input
    toggle_input = Input(
        type="checkbox",
        cls="form-check-input",
        id=toggle_id,
        checked=is_checked,
        role="switch",
        hx_post=endpoint,
        hx_trigger="change",
        hx_swap="none",  # Server handles the update
    )

    # Build label
    label_element = None
    if show_label:
        label_element = Label(
            label_text,
            cls="form-check-label ms-2",
            **{"for": toggle_id},
        )

    # Build icon (optional visual enhancement)
    icon = None
    if show_icon:
        icon_class = "bi-moon-stars-fill" if is_checked else "bi-sun-fill"
        icon = I(cls=f"bi {icon_class} me-2 fs-theme-toggle-icon", aria_hidden="true")

    # Build container
    base_classes = [
        "form-check",
        "form-switch",
        "d-flex",
        "align-items-center",
        "fs-theme-toggle",
    ]
    user_cls = kwargs.pop("cls", "")
    all_classes = merge_classes(" ".join(base_classes), user_cls)

    attrs: dict[str, Any] = {"cls": all_classes}
    attrs.update(convert_attrs(kwargs))

    # Assemble toggle
    elements = [toggle_input]
    if icon is not None:
        elements.insert(0, icon)
    if label_element:
        elements.append(label_element)

    return Div(*elements, **attrs)