Skip to content

Button

The Button component wraps standard HTML <button> or <a> elements with Bootstrap styling, loading states, and icon support.

Quick Start

Button("Get Started", variant="primary", size="lg", pill=True)

Usage Scenarios

Variants

Button("Primary", variant="primary")
Button("Secondary", variant="secondary")
Button("Success", variant="success")
Button("Danger", variant="danger")
Button("Warning", variant="warning")
Button("Info", variant="info")
Button("Light", variant="light")
Button("Dark", variant="dark")
Button("Link", variant="link")

Outline Style

Button("Delete", variant="danger", outline=True)
Button("Save Draft", variant="primary", outline=True)

Sizes

Button("Join Now!", size="lg", variant="primary")
Button("Default", variant="secondary")
Button("Details", size="sm", variant="info")

Full Width

Button("Sign In", variant="primary", full_width=True)

Anchor Tag

Button("Go to Login", as_="a", href="/login", variant="primary")

Icons

Button("Save", icon="check-circle", variant="success")
Button("Next Step", icon="arrow-right", icon_pos="end", variant="primary")

Loading State

Button("Save Profile", hx_post="/profile/save", loading_text="Saving...", loading=True)

Parameter Reference

FastStrap Param Type Bootstrap Class / Attribute Description
variant str .btn-{variant} Color theme. Options: primary, secondary, success, danger, warning, info, light, dark, link.
outline bool .btn-outline-{variant} If True, renders outline style instead of solid fill.
size str .btn-{size} Size of button. Options: sm (Small), lg (Large). Default is Medium.
full_width bool .w-100 Makes button span full width of parent.
pill bool .rounded-pill Gives button fully rounded corners.
as_ str <tag> Tag to render. Default button. Use a for links.
href str href="..." URL destination (requires as_="a").
disabled bool disabled / .disabled Disables interactivity and applies disabled styling.
active bool .active Forces the button to appear in a "pressed" state.
icon str <i class="bi bi-{icon}"> Adds a Bootstrap Icon (e.g., "check", "house").
icon_pos str - Position of icon: start (default) or end.
spinner bool .spinner-border Controls whether loading=True renders a spinner.
loading bool - Helper that enables spinner and disabled state together.
loading_text str - Text to display when loading=True.
css_vars dict style="--var: val" Dict of CSS variables to apply inline.

Accessibility

  • Buttons default to type="button" to prevent accidental form submission.
  • When rendered as an anchor (as_="a"), role="button" is added automatically.
  • Loading state sets aria-busy="true".
  • Active state sets aria-pressed="true".
  • Icons and spinners use aria-hidden="true".

API Reference

faststrap.components.forms.button.Button(*children, as_='button', variant=UNSET, size=UNSET, outline=UNSET, disabled=UNSET, loading=False, spinner=True, loading_text=None, icon=None, icon_pos='start', icon_cls=None, spinner_pos='start', spinner_cls=None, full_width=UNSET, active=UNSET, pill=UNSET, css_vars=None, style=None, **kwargs)

Bootstrap Button component.

Parameters:

Name Type Description Default
*children Any

Button content (text, elements)

()
as_ Literal['button', 'a']

Render as 'button' or 'a' tag (default: 'button')

'button'
variant VariantType | None

Bootstrap color variant

UNSET
size SizeType | None

Button size (sm, lg)

UNSET
outline bool | None

Use outline style

UNSET
disabled bool | None

Disable button

UNSET
loading bool

Show loading spinner and disable

False
spinner bool

Show spinner when loading

True
loading_text str | None

Text to show when loading

None
icon str | None

Bootstrap icon name

None
icon_pos Literal['start', 'end']

Icon position ('start' or 'end')

'start'
icon_cls str | None

Custom classes for icon

None
spinner_pos Literal['start', 'end']

Spinner position ('start' or 'end')

'start'
spinner_cls str | None

Custom classes for spinner

None
full_width bool | None

Make button 100% width. Note: Can also be achieved via cls='w-100'.

UNSET
active bool | None

Set active state. Note: Can also be achieved via cls='active'.

UNSET
pill bool | None

Use rounded-pill style. Note: Can also be achieved via cls='rounded-pill'.

UNSET
css_vars dict[str, Any] | None

Custom CSS variables dictionary

None
style dict[str, Any] | str | None

Custom inline style

None
**kwargs Any

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

{}
Source code in src/faststrap/components/forms/button.py
@register(category="forms")
def Button(
    *children: Any,
    as_: Literal["button", "a"] = "button",
    variant: VariantType | None = UNSET,
    size: SizeType | None = UNSET,
    outline: bool | None = UNSET,
    disabled: bool | None = UNSET,
    loading: bool = False,
    spinner: bool = True,
    loading_text: str | None = None,
    icon: str | None = None,
    icon_pos: Literal["start", "end"] = "start",
    icon_cls: str | None = None,
    spinner_pos: Literal["start", "end"] = "start",
    spinner_cls: str | None = None,
    full_width: bool | None = UNSET,
    active: bool | None = UNSET,
    pill: bool | None = UNSET,
    css_vars: dict[str, Any] | None = None,
    style: dict[str, Any] | str | None = None,
    **kwargs: Any,
) -> FTButton | A:
    """Bootstrap Button component.

    Args:
        *children: Button content (text, elements)
        as_: Render as 'button' or 'a' tag (default: 'button')
        variant: Bootstrap color variant
        size: Button size (sm, lg)
        outline: Use outline style
        disabled: Disable button
        loading: Show loading spinner and disable
        spinner: Show spinner when loading
        loading_text: Text to show when loading
        icon: Bootstrap icon name
        icon_pos: Icon position ('start' or 'end')
        icon_cls: Custom classes for icon
        spinner_pos: Spinner position ('start' or 'end')
        spinner_cls: Custom classes for spinner
        full_width: Make button 100% width. Note: Can also be achieved via `cls='w-100'`.
        active: Set active state. Note: Can also be achieved via `cls='active'`.
        pill: Use rounded-pill style. Note: Can also be achieved via `cls='rounded-pill'`.
        css_vars: Custom CSS variables dictionary
        style: Custom inline style
        **kwargs: Additional HTML attributes (cls, id, hx-*, data-*, etc.)
    """

    # Auto-promote to anchor if href is provided
    if as_ == "button" and "href" in kwargs:
        as_ = "a"

    # Resolve API defaults
    # This automatically picks up global defaults for 'variant', 'size', etc. if the user didn't pass them
    cfg = resolve_defaults(
        "Button",
        variant=variant,
        size=size,
        outline=outline,
        disabled=disabled,
        full_width=full_width,
        pill=pill,
        active=active,
    )

    # Extract resolved values
    c_variant = cfg.get("variant", "primary")  # Hardest fallback
    c_size = cfg.get("size")
    c_outline = cfg.get("outline", False)
    c_full_width = cfg.get("full_width", False)
    c_pill = cfg.get("pill", False)
    c_active = cfg.get("active", False)
    c_disabled = cfg.get("disabled", False)

    # Build base classes
    if c_outline and c_variant != "link":  # "link" doesn't have outline variant
        classes = [f"btn-outline-{c_variant}"]
    else:
        classes = [f"btn-{c_variant}"]

    # Add size class if specified
    if c_size:
        classes.append(f"btn-{c_size}")

    if c_full_width:
        classes.append("w-100")

    if c_pill:
        classes.append("rounded-pill")

    if c_active:
        classes.append("active")

    # Merge with user classes
    user_cls = kwargs.pop("cls", "")
    all_classes = merge_classes("btn", " ".join(classes), user_cls)

    # Build attributes with proper conversion
    attrs: dict[str, Any] = {"cls": all_classes}

    # Optional style + css vars (handled by convert_attrs)
    if style is not None:
        kwargs["style"] = style
    if css_vars is not None:
        kwargs["css_vars"] = css_vars

    # Handle states
    if c_active:
        attrs["aria_pressed"] = "true"

    if loading:
        attrs["aria_busy"] = "true"

    # Disabled/loading behavior differs for <button> vs <a>
    if loading or c_disabled:
        if as_ == "button":
            attrs["disabled"] = True
        else:
            # Bootstrap recommends `.disabled` + aria-disabled for anchors
            attrs["aria_disabled"] = "true"
            attrs["tabindex"] = "-1"
            attrs["cls"] = merge_classes(attrs.get("cls", ""), "disabled")

    # Convert remaining kwargs (including hx_*, data_*, etc.)
    attrs.update(convert_attrs(kwargs))
    if as_ == "button":
        attrs.setdefault("type", "button")

    # Build content
    content = list(children)

    # Loading: optionally override/augment text
    if loading and loading_text is not None:
        content = [loading_text]

    # Spinner
    if loading and spinner:
        if spinner_pos == "start":
            default_spinner_cls = "spinner-border spinner-border-sm me-2"
        else:
            default_spinner_cls = "spinner-border spinner-border-sm ms-2"

        spinner_elem = Span(
            cls=spinner_cls or default_spinner_cls,
            role="status",
            aria_hidden="true",
        )
        if spinner_pos == "start":
            content.insert(0, spinner_elem)
        else:
            content.append(spinner_elem)

    # Icon
    if (not loading) and icon:
        if icon_pos == "start":
            default_icon_cls = f"bi bi-{icon} me-2"
        else:
            default_icon_cls = f"bi bi-{icon} ms-2"
        icon_elem = I(cls=icon_cls or default_icon_cls, aria_hidden="true")
        if icon_pos == "start":
            content.insert(0, icon_elem)
        else:
            content.append(icon_elem)

    # Render element
    if as_ == "a":
        # Ensure href exists
        if "href" not in attrs:
            attrs["href"] = "#"
        # Keep a11y semantics when used as a button
        attrs.setdefault("role", "button")
        return A(*content, **attrs)

    return FTButton(*content, **attrs)

faststrap.components.forms.button.CloseButton(*children, white=False, aria_label='Close', **kwargs)

Bootstrap close button helper.

This is used by components like Alerts, Modals, and Drawers.

Parameters:

Name Type Description Default
*children Any

Optional children. Bootstrap typically uses an empty button.

()
white bool

Use white close icon variant (useful on dark backgrounds)

False
aria_label str

Accessible label

'Close'
**kwargs Any

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

{}

Returns:

Type Description
Button

FastHTML Button element

Source code in src/faststrap/components/forms/button.py
@register(category="forms")
def CloseButton(
    *children: Any,
    white: bool = False,
    aria_label: str = "Close",
    **kwargs: Any,
) -> FTButton:
    """Bootstrap close button helper.

    This is used by components like Alerts, Modals, and Drawers.

    Args:
        *children: Optional children. Bootstrap typically uses an empty button.
        white: Use white close icon variant (useful on dark backgrounds)
        aria_label: Accessible label
        **kwargs: Additional HTML attributes (cls, data-*, hx-*, etc.)

    Returns:
        FastHTML Button element
    """
    user_cls = kwargs.pop("cls", "")
    classes = ["btn-close"]
    if white:
        classes.append("btn-close-white")

    cls = merge_classes(" ".join(classes), user_cls)
    attrs: dict[str, Any] = {"type": "button", "cls": cls, "aria_label": aria_label}
    attrs.update(convert_attrs(kwargs))
    return FTButton(*children, **attrs)