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.


API Reference

faststrap.components.forms.multi_select.MultiSelect(name, *options, label=None, help_text=None, size=None, disabled=None, required=None, 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 = None,
    disabled: bool | None = None,
    required: bool | None = None,
    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")