Skip to content

Layout Primitives

Stack, Cluster, and Center are zero-JS flexbox layout primitives that cover the most common layout patterns in web applications. They use Bootstrap utility classes under the hood, so they are lightweight, responsive, and familiar.

Bootstrap Reference

Bootstrap 5 Flexbox


Quick Start

from faststrap import Stack, Cluster, Center, Button

# Vertical stack with gap
Stack(
    "Profile",
    "Billing",
    "Security",
    gap=3,
)

# Horizontal action row
Cluster(
    Button("Save", variant="primary"),
    Button("Cancel", variant="secondary", outline=True),
    justify="end",
)

# Centered content
Center(
    "Nothing selected yet",
    min_height="40vh",
    max_width="32rem",
)

Visual Examples & Use Cases

1. Stack (Vertical Layout)

Live Preview
Profile
Billing
Security
Stack(
    "Profile",
    "Billing",
    "Security",
    gap=3,
)

2. Cluster (Horizontal Layout)

Live Preview
Cluster(
    Button("Save", variant="primary"),
    Button("Cancel", variant="secondary", outline=True),
    justify="end",
)

3. Center (Centered Content)

Live Preview

Nothing selected yet

Center(
    "Nothing selected yet",
    min_height="40vh",
    max_width="32rem",
)

4. Nested Layouts

Live Preview
Item 1
Item 2
Stack(
    Cluster(
        Button("New", variant="primary", size="sm"),
        Button("Refresh", variant="secondary", outline=True, size="sm"),
        justify="end",
    ),
    Card(
        Card.Body(
            Stack(
                "Item 1",
                "Item 2",
                gap=2,
            )
        ),
    ),
    gap=3,
)

Practical Functionality

1. Sidebar Navigation

Stack(
    NavbarItem("Dashboard", href="/", icon="speedometer2"),
    NavbarItem("Users", href="/users", icon="people"),
    NavbarItem("Settings", href="/settings", icon="gear"),
    gap=1,
)

2. Action Bar with HTMX

Cluster(
    Button("Save", variant="primary", hx_post="/save", hx_target="#form", hx_swap="outerHTML"),
    Button("Cancel", variant="secondary", hx_get="/cancel", hx_target="#form"),
    justify="between",
)

3. Empty State

Center(
    EmptyState(
        "No data yet",
        description="Create your first item to get started.",
        icon="inbox",
    ),
    min_height="60vh",
)

Parameter Reference

Stack

Parameter Type Default Description
*children Any required Stacked content.
gap int 2 Bootstrap gap utility suffix (0–5).
align str \| None None Bootstrap align-items-* class.
justify str \| None None Bootstrap justify-content-* class.
**kwargs Any {} Additional HTML attributes.

Cluster

Parameter Type Default Description
*children Any required Clustered content.
gap int 2 Bootstrap gap utility suffix (0–5).
align str \| None None Bootstrap align-items-* class.
justify str \| None None Bootstrap justify-content-* class.
wrap bool True Allow children to wrap.
**kwargs Any {} Additional HTML attributes.

Center

Parameter Type Default Description
*children Any required Centered content.
min_height str "100vh" Minimum container height.
max_width str "100%" Maximum content width.
text_center bool True Apply text-center class.
**kwargs Any {} Additional HTML attributes.

API Reference

faststrap.components.layout.primitives.Stack(*children, gap=UNSET, align=UNSET, justify=UNSET, **kwargs)

Render a vertical flex stack with a Bootstrap gap scale.

Source code in src/faststrap/components/layout/primitives.py
@register(category="layout")
@beta
def Stack(
    *children: Any,
    gap: int | str | None = UNSET,
    align: Align | None = UNSET,
    justify: Justify | None = UNSET,
    **kwargs: Any,
) -> Div:
    """Render a vertical flex stack with a Bootstrap gap scale."""
    cfg = resolve_defaults("Stack", gap=gap, align=align, justify=justify)
    c_gap = cfg.get("gap")
    if c_gap is None:
        c_gap = 3
    c_align = cfg.get("align")
    c_justify = cfg.get("justify")

    user_cls = kwargs.pop("cls", "")
    attrs: dict[str, Any] = {
        "cls": merge_classes(
            "d-flex flex-column",
            f"gap-{c_gap}",
            _align_class(c_align),
            _justify_class(c_justify),
            user_cls,
        )
    }
    attrs.update(convert_attrs(kwargs))
    return Div(*children, **attrs)

faststrap.components.layout.primitives.Cluster(*children, gap=UNSET, align=UNSET, justify=UNSET, wrap=UNSET, **kwargs)

Render a horizontal wrapping cluster for action rows and chips.

Source code in src/faststrap/components/layout/primitives.py
@register(category="layout")
@beta
def Cluster(
    *children: Any,
    gap: int | str | None = UNSET,
    align: Align | None = UNSET,
    justify: Justify | None = UNSET,
    wrap: bool | None = UNSET,
    **kwargs: Any,
) -> Div:
    """Render a horizontal wrapping cluster for action rows and chips."""
    cfg = resolve_defaults("Cluster", gap=gap, align=align, justify=justify, wrap=wrap)
    c_gap = cfg.get("gap")
    if c_gap is None:
        c_gap = 2
    c_align = cfg.get("align") or "center"
    c_justify = cfg.get("justify")
    c_wrap = cfg.get("wrap")
    if c_wrap is None:
        c_wrap = True

    user_cls = kwargs.pop("cls", "")
    attrs: dict[str, Any] = {
        "cls": merge_classes(
            "d-flex",
            "flex-wrap" if c_wrap else "flex-nowrap",
            f"gap-{c_gap}",
            _align_class(c_align),
            _justify_class(c_justify),
            user_cls,
        )
    }
    attrs.update(convert_attrs(kwargs))
    return Div(*children, **attrs)

faststrap.components.layout.primitives.Center(*children, min_height=UNSET, max_width=UNSET, text_center=UNSET, **kwargs)

Render content centered on both axes.

Source code in src/faststrap/components/layout/primitives.py
@register(category="layout")
@beta
def Center(
    *children: Any,
    min_height: str | None = UNSET,
    max_width: str | None = UNSET,
    text_center: bool | None = UNSET,
    **kwargs: Any,
) -> Div:
    """Render content centered on both axes."""
    cfg = resolve_defaults(
        "Center",
        min_height=min_height,
        max_width=max_width,
        text_center=text_center,
    )
    c_min_height = cfg.get("min_height")
    c_max_width = cfg.get("max_width")
    c_text_center = cfg.get("text_center")
    if c_text_center is None:
        c_text_center = True

    user_cls = kwargs.pop("cls", "")
    user_style = kwargs.pop("style", None)
    styles: list[str] = []
    if c_min_height:
        styles.append(f"min-height: {c_min_height};")
    if c_max_width:
        styles.append(f"max-width: {c_max_width}; margin-inline: auto;")

    attrs: dict[str, Any] = {
        "cls": merge_classes(
            "d-flex align-items-center justify-content-center",
            "text-center" if c_text_center else "",
            user_cls,
        )
    }
    if styles or user_style:
        attrs["style"] = _merge_style(" ".join(styles), user_style)
    attrs.update(convert_attrs(kwargs))
    return Div(*children, **attrs)