Skip to content

Select

The Select component creates beautiful dropdown selection menus with full Bootstrap styling, validation support, and HTMX integration. Perfect for forms, filters, and any situation where users need to choose from a list of options.

Goal

By the end of this guide, you'll master creating single and multi-select dropdowns, understand Bootstrap form classes, and build dynamic, responsive selection interfaces—all in pure Python.


Quick Start

Live Preview
from faststrap import Select

Select(
    "country",
    ("us", "United States"),
    ("uk", "United Kingdom"),
    ("ca", "Canada"),
    ("au", "Australia"),
    label="Country"
)

Visual Examples & Use Cases

1. Sizes - Match Your Design

Bootstrap provides three sizes for select elements. Use larger selects for important choices, smaller ones for compact interfaces.

Live Preview
# Large - for prominent selections
Select("priority", ("high", "High"), ("medium", "Medium"), 
       label="Priority Level", size="lg")

# Default - standard forms
Select("category", ("tech", "Technology"), ("design", "Design"),
       label="Category")

# Small - compact interfaces, tables
Select("status", ("active", "Active"), ("inactive", "Inactive"),
       label="Status", size="sm")

When to use each size:

Size Bootstrap Class Use Case
lg .form-select-lg Hero sections, primary filters, mobile-friendly forms
Default .form-select Standard forms, most use cases
sm .form-select-sm Data tables, compact toolbars, secondary filters

2. Multiple Selection

Allow users to select multiple options by holding Ctrl/Cmd. Perfect for tags, categories, or multi-criteria filters.

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

3. Pre-selected Options

Set default selections using a third parameter in the option tuple.

Live Preview
Select(
    "theme",
    ("light", "Light Mode"),
    ("dark", "Dark Mode", True),  # ← Pre-selected
    ("auto", "Auto (System)"),
    label="Theme Preference"
)

4. Validation States

Show success, error, or warning states with Bootstrap's validation classes.

Live Preview
Great choice!
Please select an option.
# Valid state
Select(
    "plan",
    ("premium", "Premium Plan", True),
    label="Valid Selection",
    cls="is-valid"
)

# Invalid state (requires custom feedback div)
from fasthtml.common import Div

Div(
    Select(
        "required_field",
        ("", "Choose..."),
        ("opt1", "Option 1"),
        label="Invalid Selection",
        required=True,
        cls="is-invalid"
    ),
    Div("Please select an option.", cls="invalid-feedback")
)

Practical Functionality

HTMX Integration - Dynamic Filtering

Use HTMX to update content when selection changes, perfect for filters and dependent dropdowns.

Live Preview
Select a category to filter results...
from faststrap import Select, Card

@app.get("/")
def home():
    return Card(
        Select(
            "category",
            ("all", "All Categories"),
            ("tech", "Technology"),
            ("design", "Design"),
            ("business", "Business"),
            label="Filter by Category",
            hx_get="/filter",           # ← Trigger on change
            hx_target="#results",       # ← Update this element
            hx_trigger="change"         # ← When user selects
        ),
        Div(id="results", cls="alert alert-info")
    )

@app.get("/filter")
def filter_results(category: str):
    # Your filtering logic here
    return Div(f"Showing {category} results...", cls="alert alert-success")

Dependent Dropdowns

Create cascading selects where one selection determines the options in another.

@app.get("/")
def home():
    return Div(
        Select(
            "country",
            ("us", "United States"),
            ("uk", "United Kingdom"),
            ("ca", "Canada"),
            label="Country",
            hx_get="/cities",
            hx_target="#city-select",
            hx_trigger="change"
        ),
        Div(id="city-select"),  # Cities will load here
        cls="container my-4"
    )

@app.get("/cities")
def get_cities(country: str):
    cities = {
        "us": [("nyc", "New York"), ("la", "Los Angeles"), ("chi", "Chicago")],
        "uk": [("lon", "London"), ("man", "Manchester"), ("bir", "Birmingham")],
        "ca": [("tor", "Toronto"), ("van", "Vancouver"), ("mon", "Montreal")]
    }

    return Select(
        "city",
        *cities.get(country, []),
        label="City"
    )

Bootstrap CSS Classes Explained

Understanding Bootstrap's select classes helps you customize and troubleshoot your forms.

Core Classes

Class Purpose When to Use
.form-select Base class - Applies Bootstrap styling to <select> Always use on select elements
.form-select-lg Large size - Increases padding and font size Important selections, mobile-friendly forms
.form-select-sm Small size - Reduces padding and font size Compact interfaces, data tables
.form-label Label styling - Consistent label appearance Always use on <label> elements
.form-text Help text - Muted, smaller text below inputs Provide hints or instructions
.mb-3 Margin bottom - Adds spacing between form groups Wrap each select in a div with this class

Validation Classes

Class Purpose Visual Effect
.is-valid Indicates valid input Green border, checkmark icon
.is-invalid Indicates invalid input Red border, X icon
.valid-feedback Success message container Green text below select
.invalid-feedback Error message container Red text below select

State Classes

Class Purpose Effect
disabled Disables interaction Grayed out, not clickable
required Marks as required field Browser validation, asterisk in label
multiple Allows multiple selections Shows multiple options, scrollable

Responsive Design with Bootstrap

Make your selects work beautifully on all screen sizes using Bootstrap's grid system.

from faststrap import Container, Row, Col, Select

Container(
    Row(
        # Full width on mobile, half width on tablets+
        Col(
            Select(
                "first_name",
                ("mr", "Mr."), ("ms", "Ms."), ("dr", "Dr."),
                label="Title"
            ),
            cols=12,  # 100% width on mobile
            md=6      # 50% width on tablets and up
        ),
        Col(
            Select(
                "country",
                ("us", "USA"), ("uk", "UK"), ("ca", "Canada"),
                label="Country"
            ),
            cols=12,
            md=6
        )
    )
)

Bootstrap Grid Classes:

Class Breakpoint Screen Width Usage
.col-{n} All Any size Base column width (1-12)
.col-sm-{n} Small ≥576px Phone landscape
.col-md-{n} Medium ≥768px Tablets
.col-lg-{n} Large ≥992px Desktops
.col-xl-{n} Extra Large ≥1200px Large desktops

Core Faststrap Features

Global Defaults with set_component_defaults

Set default properties for all Select components in your app. Perfect for consistent styling across your application.

from faststrap import set_component_defaults, Select

# Set defaults for all selects
set_component_defaults("Select", size="lg", required=True)

# Now all selects are large and required by default
Select("country", ("us", "USA"), ("uk", "UK"), label="Country")
# ↑ Automatically has size="lg" and required=True

# Override defaults when needed
Select("optional_field", ("a", "A"), ("b", "B"), 
       label="Optional", required=False)
# ↑ Explicitly set required=False to override

Common Default Patterns:

# Admin panels - large, prominent selects
set_component_defaults("Select", size="lg")

# Data tables - compact selects
set_component_defaults("Select", size="sm")

# Forms - all fields required by default
set_component_defaults("Select", required=True)

Common Recipes

The "Please Select" Pattern

Add a placeholder option that forces users to make an active choice.

Select(
    "plan",
    ("", "-- Please Select --"),  # Empty value
    ("basic", "Basic - $9/mo"),
    ("pro", "Pro - $29/mo"),
    ("enterprise", "Enterprise - Custom"),
    label="Choose Your Plan",
    required=True
)

Grouped Options (Optgroups)

Organize related options into groups for better usability.

from fasthtml.common import Select as FTSelect, OptGroup, Option

FTSelect(
    OptGroup(
        Option("New York", value="ny"),
        Option("Los Angeles", value="la"),
        label="Popular Cities"
    ),
    OptGroup(
        Option("Chicago", value="chi"),
        Option("Houston", value="hou"),
        Option("Phoenix", value="phx"),
        label="Other Cities"
    ),
    cls="form-select",
    name="city"
)

Search Filter Select

Combine with a search input for filtering long lists.

from faststrap import Input, Select, Card

Card(
    Input(
        "search",
        placeholder="Search countries...",
        hx_get="/search-countries",
        hx_target="#country-select",
        hx_trigger="keyup changed delay:300ms"
    ),
    Div(
        Select(
            "country",
            *[(code, name) for code, name in ALL_COUNTRIES],
            label="Country"
        ),
        id="country-select"
    ),
    header="Select Country"
)

Accessibility Best Practices

Faststrap automatically handles accessibility, but here's what's happening under the hood:

Automatic Features: - Labels linked to selects via for and id attributes - Required fields marked with * and required attribute - Help text linked via aria-describedby - Semantic HTML5 <select> elements

Manual Enhancements:

Select(
    "language",
    ("en", "English"),
    ("es", "Spanish"),
    ("fr", "French"),
    label="Preferred Language",
    aria_label="Choose your preferred language",  # Screen reader description
    aria_required="true",                         # Explicitly mark as required
    help_text="This will be used for all communications"
)

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
multiple bool \| None None Allow multiple selections
cls str "" Additional CSS classes
**kwargs Any - Additional HTML attributes (id, hx-, data-, aria-*)

faststrap.components.forms.select.Select(name, *options, label=None, help_text=None, size=None, disabled=None, required=None, multiple=None, **kwargs)

Bootstrap Select component for dropdown selections.

Parameters:

Name Type Description Default
name str

Select name attribute

required
*options tuple[str, str] | tuple[str, str, bool]

Options as (value, label) or (value, label, selected)

()
label str | None

Label text

None
help_text str | None

Helper text below select

None
size SizeType | None

Select size (sm, lg)

None
disabled bool | None

Whether select is disabled

None
required bool | None

Whether select is required

None
multiple bool | None

Allow multiple selections

None
**kwargs Any

Additional HTML attributes (cls, id, hx-, data-, etc.)

{}
Source code in src/faststrap/components/forms/select.py
@register(category="forms")
def Select(
    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,
    multiple: bool | None = None,
    **kwargs: Any,
) -> Div:
    """Bootstrap Select component for dropdown selections.

    Args:
        name: Select name attribute
        *options: Options as (value, label) or (value, label, selected)
        label: Label text
        help_text: Helper text below select
        size: Select size (sm, lg)
        disabled: Whether select is disabled
        required: Whether select is required
        multiple: Allow multiple selections
        **kwargs: Additional HTML attributes (cls, id, hx-*, data-*, etc.)
    """
    # Resolve API defaults
    cfg = resolve_defaults(
        "Select", size=size, disabled=disabled, required=required, multiple=multiple
    )

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

    # Ensure ID for label linkage
    select_id = kwargs.pop("id", name)

    # Build select classes
    classes = ["form-select"]
    if c_size:
        classes.append(f"form-select-{c_size}")

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

    # Build attributes
    attrs: dict[str, Any] = {
        "cls": cls,
        "name": name,
        "id": select_id,
    }

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

    # ARIA for help text
    if help_text:
        attrs["aria_describedby"] = f"{select_id}-help"

    # Convert remaining kwargs
    attrs.update(convert_attrs(kwargs))

    # Process options
    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
        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))

    # Create select element
    select_el = FTSelect(*option_nodes, **attrs)

    # If just select (no label/help), return select only
    if not label and not help_text:
        return select_el

    # Wrap in div with label and help text
    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")