Skip to content

CalendarDatePicker

CalendarDatePicker is a single-date picker built around the native HTML date input. It is intentionally lightweight and works without custom JavaScript.

Use DateRangePicker when users need a start and end date. Use CalendarDatePicker when users choose one date, such as a publish date, appointment date, or filter date.

Import

from faststrap import CalendarDatePicker

Basic Usage

CalendarDatePicker(
    "publish_date",
    label="Publish date",
    value="2026-05-07",
)

Date Limits

CalendarDatePicker(
    "appointment_date",
    label="Appointment date",
    min_date="2026-05-01",
    max_date="2026-05-31",
)

HTMX Filtering

CalendarDatePicker(
    "day",
    label="Activity day",
    endpoint="/activity",
    hx_target="#activity-feed",
    auto=True,
    push_url=True,
)

When auto=True, the picker submits after the selected date changes.

With Clear Button

CalendarDatePicker(
    "due_date",
    label="Due date",
    clear_label="Clear",
)

Parameters

Param Type Default Description
name str "date" Input name.
label str "Date" Label shown above the date input.
value str \| None None Initial date in YYYY-MM-DD format.
min_date str \| None None Earliest selectable date.
max_date str \| None None Latest selectable date.
endpoint str \| None None Optional form/HTMX endpoint.
method get \| post "get" Submission method.
auto bool False Submit on date change when an endpoint is present.
apply_label str \| None "Apply" Submit button label. Set to None to hide.
clear_label str \| None None Optional reset button label.
apply_cls / clear_cls str \| None None Extra classes for the Apply/Clear buttons (e.g. btn-sm).
hx_target str \| None None HTMX target for responses.
hx_swap str \| None "outerHTML" HTMX swap style.
push_url bool False Whether HTMX should push the URL.
input_cls / form_cls str \| None None Styling hooks.
**kwargs Any Extra wrapper attributes.

The input control and the action buttons share one horizontal baseline; GET forms submit via the query string.

faststrap.components.forms.calendar_date_picker.CalendarDatePicker(name='date', *, label='Date', value=None, min_date=None, max_date=None, endpoint=None, method='get', auto=False, apply_label='Apply', clear_label=None, hx_target=None, hx_swap='outerHTML', push_url=False, input_cls=None, form_cls=None, apply_cls=None, clear_cls=None, **kwargs)

Render a single date picker around the native HTML date input.

Parameters:

Name Type Description Default
name str

Input name attribute.

'date'
label str

Label shown above the date input.

'Date'
value str | None

Initial date in YYYY-MM-DD format.

None
min_date str | None

Earliest selectable date.

None
max_date str | None

Latest selectable date.

None
endpoint str | None

Optional form/HTMX endpoint.

None
method CalendarMethod

Submission method.

'get'
auto bool

Submit on date change when an endpoint is present.

False
apply_label str | None

Submit button label; None hides it.

'Apply'
clear_label str | None

Optional reset button label.

None
hx_target str | None

HTMX target for responses.

None
hx_swap str | None

HTMX swap style.

'outerHTML'
push_url bool

Whether HTMX should push the URL.

False
input_cls str | None

Extra classes for the date input.

None
form_cls str | None

Extra classes for the wrapping form row.

None
apply_cls str | None

Extra classes for the Apply button.

None
clear_cls str | None

Extra classes for the Clear button.

None
**kwargs Any

Additional HTML attributes for the wrapper.

{}
Source code in src/faststrap/components/forms/calendar_date_picker.py
@register(category="forms")
@stable
def CalendarDatePicker(
    name: str = "date",
    *,
    label: str = "Date",
    value: str | None = None,
    min_date: str | None = None,
    max_date: str | None = None,
    endpoint: str | None = None,
    method: CalendarMethod = "get",
    auto: bool = False,
    apply_label: str | None = "Apply",
    clear_label: str | None = None,
    hx_target: str | None = None,
    hx_swap: str | None = "outerHTML",
    push_url: bool = False,
    input_cls: str | None = None,
    form_cls: str | None = None,
    apply_cls: str | None = None,
    clear_cls: str | None = None,
    **kwargs: Any,
) -> Div:
    """Render a single date picker around the native HTML date input.

    Args:
        name: Input ``name`` attribute.
        label: Label shown above the date input.
        value: Initial date in ``YYYY-MM-DD`` format.
        min_date: Earliest selectable date.
        max_date: Latest selectable date.
        endpoint: Optional form/HTMX endpoint.
        method: Submission method.
        auto: Submit on date change when an endpoint is present.
        apply_label: Submit button label; ``None`` hides it.
        clear_label: Optional reset button label.
        hx_target: HTMX target for responses.
        hx_swap: HTMX swap style.
        push_url: Whether HTMX should push the URL.
        input_cls: Extra classes for the date input.
        form_cls: Extra classes for the wrapping form row.
        apply_cls: Extra classes for the Apply button.
        clear_cls: Extra classes for the Clear button.
        **kwargs: Additional HTML attributes for the wrapper.
    """
    if method not in {"get", "post"}:
        msg = f"method must be 'get' or 'post', got {method!r}"
        raise ValueError(msg)

    user_cls = kwargs.pop("cls", "")
    attrs: dict[str, Any] = {
        "cls": merge_classes("faststrap-calendar-date-picker", user_cls),
        "data_fs_calendar_date_picker": "true",
    }
    attrs.update(convert_attrs(kwargs))

    date_input = Input(
        name,
        input_type="date",
        label=label,
        value=value,
        min=min_date,
        max=max_date,
        cls=input_cls,
    )
    # Neutralize Input's default `mb-3` wrapper margin so the date control and
    # the action buttons share one clean horizontal baseline (flexbox
    # bottom-aligns against margin boxes otherwise).
    wrapper_attrs = getattr(date_input, "attrs", None)
    if wrapper_attrs is not None:
        for key in ("cls", "class"):
            existing = wrapper_attrs.get(key) or ""
            if "mb-3" in existing:
                remaining = existing.replace("mb-3", "").strip()
                if remaining:
                    wrapper_attrs[key] = remaining
                else:
                    wrapper_attrs.pop(key, None)
                break

    form_attrs: dict[str, Any] = {
        "method": method,
        "cls": merge_classes("d-flex flex-wrap align-items-end gap-2", form_cls),
    }
    if endpoint:
        form_attrs["action"] = endpoint
        form_attrs[f"hx_{method}"] = endpoint
        if hx_target:
            form_attrs["hx_target"] = hx_target
        if hx_swap:
            form_attrs["hx_swap"] = hx_swap
        if push_url:
            form_attrs["hx_push_url"] = "true"
        if auto:
            form_attrs["hx_trigger"] = "change delay:300ms"

    controls: list[Any] = [date_input]
    if apply_label:
        controls.append(Button(apply_label, type="submit", variant="primary", cls=apply_cls))
    if clear_label:
        controls.append(
            Button(
                clear_label,
                type="reset",
                variant="secondary",
                outline=True,
                cls=clear_cls,
            )
        )

    converted_form_attrs = convert_attrs(form_attrs)
    # GET forms submit via the query string; the multipart encoding default is
    # meaningless there. Applied after conversion because convert_attrs drops
    # None values.
    if method == "get":
        converted_form_attrs["enctype"] = None

    return Div(FTForm(*controls, **converted_form_attrs), **attrs)