Skip to content

FilterBar

Composable filter layout with HTMX integration and optional apply mode.


Quick Start

Live Preview
from faststrap import FilterBar, MultiSelect, RangeSlider

FilterBar(
    MultiSelect("team", ("ops", "Ops"), ("data", "Data")),
    RangeSlider("score", min_value=0, max_value=100),
    mode="apply",
)

Auto vs Apply Mode

  • mode="auto" triggers HTMX on change and keyup
  • mode="apply" renders an Apply button
FilterBar(
    *filters,
    endpoint="/reports",
    mode="auto",
    debounce=400,
)

Reset Button

FilterBar(
    *filters,
    mode="apply",
    reset_label="Reset",
    reset_href="/reports",
)

HTMX Integration

FilterBar(
    *filters,
    endpoint="/reports",
    method="get",
    hx_target="#results",
    hx_swap="outerHTML",
    push_url=True,
)

Layout Control

Use filters_cls and actions_cls to adjust spacing or alignment.

FilterBar(
    *filters,
    filters_cls="gap-2",
    actions_cls="ms-auto",
)

Security Notes

Validate filters server-side. If you use method="post", enable CSRF protection.

Parameters

Parameter Type Default Description
*filters Any Filter controls to render.
endpoint str \| None None Form action and optional HTMX endpoint.
method "get" \| "post" "get" Submit method.
mode "auto" \| "apply" "auto" Auto-submit on change or render Apply button.
apply_label str "Apply" Apply button text in apply mode.
apply_variant Bootstrap variant "primary" Apply button variant.
reset_label str \| None None Reset link label.
reset_href str \| None None Reset link target.
debounce int 300 HTMX debounce in milliseconds for auto mode.
hx_target str \| None None HTMX target selector.
hx_swap str \| None "outerHTML" HTMX swap style.
push_url bool False Push URL for HTMX GET flows.
filters_cls / actions_cls / form_cls str \| None None Styling hooks.
**kwargs Any Extra form attributes.

API Reference

faststrap.components.forms.filter_bar.FilterBar(*filters, endpoint=None, method='get', mode='auto', apply_label='Apply', apply_variant='primary', reset_label=None, reset_href=None, debounce=300, hx_target=None, hx_swap='outerHTML', push_url=False, filters_cls=None, actions_cls=None, form_cls=None, **kwargs)

Composable filter bar with optional HTMX integration.

Source code in src/faststrap/components/forms/filter_bar.py
@register(category="forms")
@beta
def FilterBar(
    *filters: Any,
    endpoint: str | None = None,
    method: FilterMethod = "get",
    mode: FilterMode = "auto",
    apply_label: str = "Apply",
    apply_variant: VariantType = "primary",
    reset_label: str | None = None,
    reset_href: str | None = None,
    debounce: int = 300,
    hx_target: str | None = None,
    hx_swap: str | None = "outerHTML",
    push_url: bool = False,
    filters_cls: str | None = None,
    actions_cls: str | None = None,
    form_cls: str | None = None,
    **kwargs: Any,
) -> FTForm:
    """Composable filter bar with optional HTMX integration."""
    cfg = resolve_defaults(
        "FilterBar",
        method=method,
        mode=mode,
        apply_label=apply_label,
        apply_variant=apply_variant,
        debounce=debounce,
        hx_swap=hx_swap,
        push_url=push_url,
    )
    c_method = cfg.get("method", method)
    c_mode = cfg.get("mode", mode)
    c_apply_label = cfg.get("apply_label", apply_label)
    c_apply_variant = cfg.get("apply_variant", apply_variant)
    c_debounce = cfg.get("debounce", debounce)
    c_hx_swap = cfg.get("hx_swap", hx_swap)
    c_push_url = cfg.get("push_url", push_url)

    if c_method not in {"get", "post"}:
        msg = f"method must be 'get' or 'post', got {c_method}"
        raise ValueError(msg)
    if c_mode not in {"auto", "apply"}:
        msg = f"mode must be 'auto' or 'apply', got {c_mode}"
        raise ValueError(msg)

    form_attrs: dict[str, Any] = {
        "method": c_method,
        "cls": merge_classes("faststrap-filter-bar", form_cls),
    }

    if endpoint:
        form_attrs["action"] = endpoint
        if c_method == "get":
            form_attrs["hx_get"] = endpoint
        else:
            form_attrs["hx_post"] = endpoint
        if hx_target:
            form_attrs["hx_target"] = hx_target
        if c_hx_swap:
            form_attrs["hx_swap"] = c_hx_swap
        if c_push_url:
            form_attrs["hx_push_url"] = "true"
        if c_mode == "auto":
            form_attrs["hx_trigger"] = (
                f"change delay:{c_debounce}ms, keyup changed delay:{c_debounce}ms"
            )

    filters_wrap = Div(
        *filters,
        cls=merge_classes("d-flex flex-wrap gap-3 align-items-end", filters_cls),
    )

    actions: list[Any] = []
    if c_mode == "apply":
        actions.append(Button(c_apply_label, type="submit", variant=c_apply_variant))
    if reset_label and reset_href:
        actions.append(Button(reset_label, variant="secondary", outline=True, href=reset_href))

    content: list[Any] = [filters_wrap]
    if actions:
        content.append(Div(*actions, cls=merge_classes("ms-auto d-flex gap-2", actions_cls)))

    form_attrs.update(convert_attrs(kwargs))

    return FTForm(*content, **form_attrs)