Skip to content

Multi Select

The MultiSelect component creates a dropdown that allows selecting multiple options.

Quick Start

MultiSelect(
    "skills",
    ("python", "Python"),
    ("javascript", "JavaScript"),
    ("html", "HTML/CSS"),
    ("sql", "SQL"),
    ("docker", "Docker"),
    label="Skills",
    help_text="Hold Ctrl/Cmd to select multiple",
)

Usage Scenarios

Pre-selected Values

MultiSelect(
    "tags",
    ("python", "Python"),
    ("javascript", "JavaScript"),
    ("react", "React"),
    selected=["python", "react"],
    label="Tags",
)

With Validation State

MultiSelect(
    "categories",
    ("tech", "Technology"),
    ("design", "Design"),
    label="Categories",
    required=True,
    disabled=False,
)

Parameter Reference

Parameter Type Default Description
name str Required Form field name attribute
*options tuple Required Options as (value, label) or (value, label, selected)
label str \| None None Label text above select
help_text str \| None None Helper text below select
size "sm" \| "lg" \| None None Select size (default is medium)
disabled bool \| None None Whether select is disabled
required bool \| None None Whether select is required
selected Iterable[str] \| None None Pre-selected values
cls str "" Additional CSS classes
**kwargs Any - Additional HTML attributes (id, hx-, data-, aria-*)

Accessibility

  • Labels are linked to selects via for and id attributes.
  • Required fields are marked with required attribute and a red asterisk in the label.
  • Help text is linked via aria-describedby.
  • The multiple attribute is automatically added for multi-selection.

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")