Skip to content

ToggleGroup

ToggleGroup makes a group of buttons behave like a single-select control.

It is ideal when you want radio-like visual selection behavior, but with custom button styling and no custom JavaScript setup in app code.

Import

from faststrap import ToggleGroup, Button

Basic usage

ToggleGroup(
    Button("Newest", variant="outline-primary"),
    Button("Popular", variant="outline-primary"),
    Button("Top Rated", variant="outline-primary"),
)

Only one button stays active at a time.

Why this is useful

  • Removes repeated "activate one, deactivate others" boilerplate.
  • Keeps your sort/filter/tab selectors visually consistent.
  • Works with existing Button(...) variants and custom classes.

Common use cases

  1. Sorting controls: Newest / Popular / Trending.
  2. Pricing toggles: Monthly / Yearly.
  3. View mode selectors: Grid / List / Compact.
  4. Lightweight tab-like controls when you only need active visuals.

With submitted form value

ToggleGroup(
    Button("Newest", variant="outline-secondary"),
    Button("Popular", variant="outline-secondary"),
    name="sort",
    values=["new", "popular"],
    active_index=0,
)

This adds a hidden input so the selected value submits with your form.

HTMX integration example

ToggleGroup(
    Button("Newest", variant="outline-primary", hx_get="/posts?sort=new", hx_target="#posts"),
    Button("Popular", variant="outline-primary", hx_get="/posts?sort=popular", hx_target="#posts"),
    Button("Top", variant="outline-primary", hx_get="/posts?sort=top", hx_target="#posts"),
    name="sort",
    values=["new", "popular", "top"],
)

ToggleGroup handles active visual state. Your route logic still decides behavior and response.

API

  • *buttons: Buttons or clickable elements.
  • name: Optional field name for hidden input.
  • values: Optional submitted values per button.
  • active_index: Initially active button index.
  • active_cls: Active class name (default: "active").
  • hidden_input: Whether to include hidden input when name is provided.

Notes

  • If values is provided, it must match the number of buttons.
  • If name is set, selected value is synced to hidden input for form submission.

API Reference

faststrap.components.forms.toggle_group.ToggleGroup(*buttons, name=None, values=None, active_index=0, active_cls='active', hidden_input=True, **kwargs)

Render a button group where only one item stays active at a time.

Source code in src/faststrap/components/forms/toggle_group.py
@register(category="forms")
def ToggleGroup(
    *buttons: Any,
    name: str | None = None,
    values: list[str] | None = None,
    active_index: int = 0,
    active_cls: str = "active",
    hidden_input: bool = True,
    **kwargs: Any,
) -> Div:
    """Render a button group where only one item stays active at a time."""
    btn_list = list(buttons)
    if not btn_list:
        raise ValueError("ToggleGroup requires at least one button.")
    if values is not None and len(values) != len(btn_list):
        raise ValueError("values length must match number of buttons.")

    input_id = f"fs-toggle-{uuid4().hex[:8]}"
    safe_active_index = max(0, min(active_index, len(btn_list) - 1))

    for i, button in enumerate(btn_list):
        if not hasattr(button, "attrs"):
            continue
        button_attrs = button.attrs
        button_attrs["data-fs-toggle-item"] = "true"
        button_attrs["data-fs-value"] = values[i] if values is not None else str(i)
        current_cls = button_attrs.get("cls", "")
        if i == safe_active_index:
            button_attrs["cls"] = merge_classes(current_cls, active_cls)
            button_attrs["aria-pressed"] = "true"
            button_attrs["aria-current"] = "true"
        else:
            button_attrs["aria-pressed"] = "false"
            button_attrs["aria-current"] = "false"

    user_cls = kwargs.pop("cls", "")
    attrs: dict[str, Any] = {
        "cls": merge_classes("btn-group", user_cls),
        "role": "group",
        "data_fs_toggle_group": "true",
        "data_fs_active_class": active_cls,
    }
    if hidden_input and name:
        attrs["data_fs_input_id"] = input_id
    attrs.update(convert_attrs(kwargs))

    children: list[Any] = btn_list
    if hidden_input and name:
        selected = values[safe_active_index] if values else str(safe_active_index)
        children.append(Input(type="hidden", name=name, value=selected, id=input_id))
    return Div(*children, **attrs)