Skip to content

Form

The Form component is a compatibility wrapper around FastHTML's native Form element. It ensures wildcard imports do not accidentally break ordinary form markup, and it provides FormBuilder.from_pydantic() for generating Bootstrap-styled forms from Pydantic models.

For most use cases, you will use FastHTML's native Form directly, or FormBuilder.from_pydantic() for generated forms.

Bootstrap Reference

Bootstrap 5 Forms Documentation


Quick Start

Native Form

from fasthtml.common import Form
from faststrap import FormGroup, Input, Button

Form(
    FormGroup("Email", Input(name="email", type="email", required=True)),
    FormGroup("Password", Input(name="password", type="password", required=True)),
    Button("Sign In", type="submit", variant="primary"),
    method="post",
    action="/login",
)

Generated from Pydantic

from faststrap import FormBuilder

FormBuilder.from_pydantic(
    MyPydanticModel,
    action="/submit",
    method="post",
    submit_label="Save",
    submit_variant="primary",
)

Visual Examples & Use Cases

1. Basic POST Form

Live Preview
Form(
    FormGroup("Email", Input(name="email", type="email", required=True)),
    FormGroup("Password", Input(name="password", type="password", required=True)),
    Button("Sign In", type="submit", variant="primary"),
    method="post",
    action="/login",
)

2. Pydantic-Generated Form

Live Preview
from pydantic import BaseModel

class UserForm(BaseModel):
    name: str
    age: int

FormBuilder.from_pydantic(
    UserForm,
    submit_label="Save",
    submit_variant="primary",
)

3. Form with HTMX

Live Preview
Form(
    FormGroup("Search", Input(name="q", type="search", placeholder="Search...")),
    Button("Search", type="submit", variant="primary"),
    method="post",
    hx_post="/search",
    hx_target="#results",
    hx_swap="innerHTML",
)

4. File Upload Form

Live Preview
Form(
    FormGroup("Upload", FileInput(name="file")),
    Button("Upload", type="submit", variant="primary"),
    method="post",
    enctype="multipart/form-data",
)

Practical Functionality

1. Form Validation

Faststrap provides several utilities for server-side validation:

from faststrap import FormErrorSummary, FormGroupFromErrors, map_formgroup_validation

# In your route handler:
@app.post("/login")
def login(email: str, password: str):
    errors = validate_login(email, password)
    if errors:
        return FormErrorSummary(errors), FormGroupFromErrors(errors, "email")
    return RedirectResponse("/dashboard")

2. Live Validation with HTMX

from faststrap import LiveValidationField

Form(
    LiveValidationField(
        Input(name="username"),
        validate_url="/validate/username",
        label="Username",
    ),
)

3. OTP Input Inside Forms

from faststrap import Form, FormGroup, OTPInput, Button

Form(
    FormGroup("Verification Code", OTPInput(length=6, name="otp")),
    Button("Verify", type="submit", variant="primary"),
    method="post",
    action="/verify",
)

Parameter Reference

FormBuilder.from_pydantic

Parameter Type Default Description
model_class type[Any] required Pydantic BaseModel class
action str \| None None Form action URL
method str "post" HTTP method
include list[str] \| None None Fields to include
exclude list[str] \| None None Fields to exclude
submit_label str "Submit" Submit button text
submit_variant str "primary" Submit button Bootstrap variant
form_cls str "" Extra CSS classes for form
button_cls str "" Extra CSS classes for submit button
**kwargs Any {} Additional HTML attributes

Form (Native Wrapper)

The Form wrapper accepts the same arguments as FastHTML's native Form:

Parameter Type Default Description
*children Any required Form fields and content
method str "post" HTTP method
action str \| None None Form action URL
enctype str "application/x-www-form-urlencoded" Encoding type
**kwargs Any {} Additional HTML attributes (hx_, data_, etc.)

API Reference

faststrap.components.forms.form.FormBuilder

Form builder helpers.

Source code in src/faststrap/components/forms/form.py
@beta
class FormBuilder:
    """Form builder helpers."""

    @staticmethod
    def from_pydantic(
        model_class: type[Any],
        *,
        action: str | None = None,
        method: str = "post",
        include: list[str] | None = None,
        exclude: list[str] | None = None,
        submit_label: str = "Submit",
        submit_variant: str = "primary",
        form_cls: str = "",
        button_cls: str = "",
        **kwargs: Any,
    ) -> FTForm:
        """Generate a Bootstrap-styled form from a Pydantic model class."""
        try:
            pydantic_module = importlib.import_module("pydantic")
        except ImportError as exc:
            msg = (
                "FormBuilder.from_pydantic() requires pydantic. "
                "Install with `pip install pydantic`."
            )
            raise ImportError(msg) from exc

        base_model = pydantic_module.BaseModel
        if not isinstance(model_class, type) or not issubclass(model_class, base_model):
            msg = "FormBuilder.from_pydantic() expects a Pydantic BaseModel class."
            raise TypeError(msg)

        model_fields = getattr(model_class, "model_fields", None)
        if not isinstance(model_fields, dict):
            msg = "Unsupported Pydantic model format."
            raise TypeError(msg)

        include_set = set(include or model_fields.keys())
        exclude_set = set(exclude or [])

        fields: list[Any] = []
        for name, field_info in model_fields.items():
            if name not in include_set or name in exclude_set:
                continue

            input_element, required = _build_field_input(name, field_info)
            label = getattr(field_info, "title", None) or _pretty_label(name)
            description = getattr(field_info, "description", None)
            fields.append(
                FormGroup(
                    input_element,
                    label=label,
                    help_text=description,
                    required=required,
                )
            )

        fields.append(
            FTButton(
                submit_label,
                type="submit",
                cls=f"btn btn-{submit_variant} {button_cls}".strip(),
            )
        )

        form_attrs: dict[str, Any] = {
            "method": method,
            "cls": f"faststrap-generated-form {form_cls}".strip(),
        }
        if action:
            form_attrs["action"] = action
        form_attrs.update(kwargs)
        form_attrs = convert_attrs(form_attrs)

        return FTForm(*fields, **form_attrs)
from_pydantic(model_class, *, action=None, method='post', include=None, exclude=None, submit_label='Submit', submit_variant='primary', form_cls='', button_cls='', **kwargs) staticmethod

Generate a Bootstrap-styled form from a Pydantic model class.

Source code in src/faststrap/components/forms/form.py
@staticmethod
def from_pydantic(
    model_class: type[Any],
    *,
    action: str | None = None,
    method: str = "post",
    include: list[str] | None = None,
    exclude: list[str] | None = None,
    submit_label: str = "Submit",
    submit_variant: str = "primary",
    form_cls: str = "",
    button_cls: str = "",
    **kwargs: Any,
) -> FTForm:
    """Generate a Bootstrap-styled form from a Pydantic model class."""
    try:
        pydantic_module = importlib.import_module("pydantic")
    except ImportError as exc:
        msg = (
            "FormBuilder.from_pydantic() requires pydantic. "
            "Install with `pip install pydantic`."
        )
        raise ImportError(msg) from exc

    base_model = pydantic_module.BaseModel
    if not isinstance(model_class, type) or not issubclass(model_class, base_model):
        msg = "FormBuilder.from_pydantic() expects a Pydantic BaseModel class."
        raise TypeError(msg)

    model_fields = getattr(model_class, "model_fields", None)
    if not isinstance(model_fields, dict):
        msg = "Unsupported Pydantic model format."
        raise TypeError(msg)

    include_set = set(include or model_fields.keys())
    exclude_set = set(exclude or [])

    fields: list[Any] = []
    for name, field_info in model_fields.items():
        if name not in include_set or name in exclude_set:
            continue

        input_element, required = _build_field_input(name, field_info)
        label = getattr(field_info, "title", None) or _pretty_label(name)
        description = getattr(field_info, "description", None)
        fields.append(
            FormGroup(
                input_element,
                label=label,
                help_text=description,
                required=required,
            )
        )

    fields.append(
        FTButton(
            submit_label,
            type="submit",
            cls=f"btn btn-{submit_variant} {button_cls}".strip(),
        )
    )

    form_attrs: dict[str, Any] = {
        "method": method,
        "cls": f"faststrap-generated-form {form_cls}".strip(),
    }
    if action:
        form_attrs["action"] = action
    form_attrs.update(kwargs)
    form_attrs = convert_attrs(form_attrs)

    return FTForm(*fields, **form_attrs)

faststrap.components.forms.form.Form

Compatibility wrapper for FastHTML's native Form element.

Form(...) delegates to FastHTML's native Form so wildcard imports do not accidentally break ordinary form markup. Form.from_pydantic() is retained for backward compatibility with Faststrap v0.6.0 and earlier. New code should prefer FormBuilder.from_pydantic() for generated forms.

Source code in src/faststrap/components/forms/form.py
@beta
class Form:
    """Compatibility wrapper for FastHTML's native `Form` element.

    `Form(...)` delegates to FastHTML's native `Form` so wildcard imports do
    not accidentally break ordinary form markup. `Form.from_pydantic()` is
    retained for backward compatibility with Faststrap v0.6.0 and earlier.
    New code should prefer `FormBuilder.from_pydantic()` for generated forms.
    """

    def __new__(cls, *children: Any, **kwargs: Any) -> FTForm:
        return FTForm(*children, **kwargs)

    @staticmethod
    def from_pydantic(
        model_class: type[Any],
        *,
        action: str | None = None,
        method: str = "post",
        include: list[str] | None = None,
        exclude: list[str] | None = None,
        submit_label: str = "Submit",
        submit_variant: str = "primary",
        form_cls: str = "",
        button_cls: str = "",
        **kwargs: Any,
    ) -> FTForm:
        warnings.warn(
            "Form.from_pydantic() is deprecated as of Faststrap v0.6.1; "
            "use FormBuilder.from_pydantic() instead. "
            "The Form alias remains supported for backward compatibility.",
            DeprecationWarning,
            stacklevel=2,
        )
        return FormBuilder.from_pydantic(
            model_class,
            action=action,
            method=method,
            include=include,
            exclude=exclude,
            submit_label=submit_label,
            submit_variant=submit_variant,
            form_cls=form_cls,
            button_cls=button_cls,
            **kwargs,
        )