Skip to content

MultiSelect

Bootstrap multi-select built for FastHTML.


Quick Start

Live Preview
from faststrap import MultiSelect

MultiSelect(
    "team",
    ("platform", "Platform"),
    ("ops", "Ops"),
    ("data", "Data"),
    selected=["data"],
    label="Teams",
)

Sizes and States

MultiSelect(
    "team",
    ("a", "A"),
    ("b", "B"),
    size="sm",
    required=True,
)

Help Text

MultiSelect(
    "team",
    ("a", "A"),
    ("b", "B"),
    help_text="Choose one or more teams.",
)

Server Handling

Multi-select inputs submit multiple values. On the server, use the framework's list access for query params or form data.

Parameters

Parameter Type Default Description
name str required Select input name and default ID.
*options (value, label) or (value, label, selected) Options to render.
label str \| None None Optional field label.
help_text str \| None None Optional helper text under the field.
size sm \| lg \| None UNSET Bootstrap select size.
disabled bool \| None UNSET Disable the select.
required bool \| None UNSET Mark the select required.
selected Iterable[str] \| None None Values selected when using two-item option tuples.
**kwargs Any Extra select attributes.

Option Selection Rules

  • Use selected=["data"] when your options are two-tuples.
  • Use three-tuples like ("data", "Data", True) when each option carries its own selected state.
  • Invalid option tuple lengths raise ValueError.

API Reference

faststrap.components.forms.multi_select.MultiSelect(name, *options, label=None, help_text=None, size=UNSET, disabled=UNSET, required=UNSET, selected=None, **kwargs)

Bootstrap MultiSelect component.

Source code in src/faststrap/components/forms/multi_select.py
@register(category="forms")
def MultiSelect(
    name: str,
    *options: tuple[str, str] | tuple[str, str, bool],
    label: str | None = None,
    help_text: str | None = None,
    size: SizeType | None = UNSET,
    disabled: bool | None = UNSET,
    required: bool | None = UNSET,
    selected: Iterable[str] | None = None,
    **kwargs: Any,
) -> Div:
    """Bootstrap MultiSelect component."""
    cfg = resolve_defaults("MultiSelect", size=size, disabled=disabled, required=required)

    c_size = cfg.get("size")
    c_disabled = cfg.get("disabled", False)
    c_required = cfg.get("required", False)

    select_id = kwargs.pop("id", name)
    selected_set = {str(item) for item in selected} if selected else set()

    classes = ["form-select", "faststrap-multi-select"]
    if c_size:
        classes.append(f"form-select-{c_size}")

    user_cls = kwargs.pop("cls", "")
    cls = merge_classes(" ".join(classes), user_cls)

    attrs: dict[str, Any] = {
        "cls": cls,
        "name": name,
        "id": select_id,
        "multiple": True,
    }

    if c_disabled:
        attrs["disabled"] = True
    if c_required:
        attrs["required"] = True

    if help_text:
        attrs["aria_describedby"] = f"{select_id}-help"

    attrs.update(convert_attrs(kwargs))

    option_nodes: list[Any] = []
    for item in options:
        is_selected = False
        if len(item) == 3:
            value, label_text, is_selected = item
        elif len(item) == 2:
            value, label_text = item
            is_selected = str(value) in selected_set
        else:
            raise ValueError(
                f"Option must be (value, label) or (value, label, selected), got {item}"
            )

        opt_attrs: dict[str, Any] = {"value": value}
        if is_selected:
            opt_attrs["selected"] = True

        option_nodes.append(Option(label_text, **opt_attrs))

    select_el = FTSelect(*option_nodes, **attrs)

    if not label and not help_text:
        return select_el

    nodes: list[Any] = []
    if label:
        nodes.append(
            Label(
                label,
                " ",
                Small("*", cls="text-danger") if c_required else "",
                **{"for": select_id},
                cls="form-label",
            )
        )

    nodes.append(select_el)

    if help_text:
        help_id = f"{select_id}-help"
        nodes.append(Small(help_text, cls="form-text text-muted", id=help_id))

    return Div(*nodes, cls="mb-3")