Skip to content

Drawer (Offcanvas)

The Drawer component creates sliding side panels for navigation menus, settings, and additional content. Perfect for mobile-friendly navigation and contextual information displays.

Goal

Master creating drawer panels, understand Bootstrap offcanvas classes, and build responsive navigation that works beautifully on all screen sizes.


Quick Start

from faststrap import Drawer, Button

# Button to trigger drawer
Button(
    "Open Menu",
    variant="primary",
    data_bs_toggle="offcanvas",
    data_bs_target="#myDrawer"
)

# Drawer component
Drawer(
    ListGroup(
        ListGroupItem("Home", href="/"),
        ListGroupItem("About", href="/about"),
        ListGroupItem("Contact", href="/contact")
    ),
    drawer_id="myDrawer",
    title="Navigation",
    placement="start"  # Left side
)

Visual Examples & Use Cases

1. Placements - Four Directions

# Left drawer (default)
Drawer(content, drawer_id="left", title="Left Menu", placement="start")

# Right drawer
Drawer(content, drawer_id="right", title="Right Menu", placement="end")

# Top drawer
Drawer(content, drawer_id="top", title="Top Panel", placement="top")

# Bottom drawer
Drawer(content, drawer_id="bottom", title="Bottom Panel", placement="bottom")

2. Mobile Navigation Menu

from faststrap import Drawer, ListGroup, ListGroupItem, Icon, Button

# Trigger button
Button(
    Icon("list"),
    variant="outline-primary",
    data_bs_toggle="offcanvas",
    data_bs_target="#mobileNav"
)

# Drawer
Drawer(
    ListGroup(
        ListGroupItem(Icon("house"), " Home", href="/", action=True),
        ListGroupItem(Icon("grid"), " Products", href="/products", action=True),
        ListGroupItem(Icon("info-circle"), " About", href="/about", action=True),
        ListGroupItem(Icon("envelope"), " Contact", href="/contact", action=True),
        flush=True
    ),
    drawer_id="mobileNav",
    title="Menu",
    placement="start"
)

3. Settings Panel

Drawer(
    H5("Preferences"),
    Switch("dark_mode", label="Dark Mode"),
    Switch("notifications", label="Notifications"),
    Select(
        "language",
        ("en", "English"),
        ("es", "Spanish"),
        label="Language"
    ),
    Button("Save", variant="primary", cls="w-100 mt-3"),
    drawer_id="settings",
    title="Settings",
    placement="end"
)

Focus Trap (Accessibility)

Trap keyboard focus inside the drawer and optionally set autofocus:

Drawer(
    "Drawer content",
    drawer_id="settings",
    title="Settings",
    focus_trap=True,
    autofocus_selector="#first-input",
)

Bootstrap CSS Classes Explained

Class Purpose
.offcanvas Base drawer container
.offcanvas-start Left placement
.offcanvas-end Right placement
.offcanvas-top Top placement
.offcanvas-bottom Bottom placement
.offcanvas-header Drawer header
.offcanvas-body Drawer content
.offcanvas-title Title text

Parameter Reference

Parameter Type Default Description
*children Any Required Drawer body content
drawer_id str \| None Auto-generated Unique ID for drawer
title str \| None None Drawer header title
placement "start" \| "end" \| "top" \| "bottom" "start" Drawer position
backdrop bool \| None True Show backdrop overlay
scroll bool \| None False Allow body scroll when open
dark bool \| None False Dark variant
**kwargs Any - Additional HTML attributes

faststrap.components.navigation.drawer.Drawer(*children, drawer_id=None, title=None, footer=None, placement=None, backdrop=None, scroll=None, dark=None, focus_trap=None, autofocus_selector=None, header_cls=None, body_cls=None, footer_cls=None, title_cls=None, close_cls=None, **kwargs)

Bootstrap Offcanvas (Drawer) component for side panels and menus.

Parameters:

Name Type Description Default
*children Any

Drawer body content

()
drawer_id str | None

Unique ID for the drawer (required for Bootstrap JS)

None
title str | None

Drawer header title

None
footer Any | None

Drawer footer content

None
placement PlacementType | None

Drawer position (start=left, end=right, top, bottom)

None
backdrop bool | None

Show backdrop overlay

None
scroll bool | None

Allow body scroll when drawer is open

None
dark bool | None

Use dark variant (Bootstrap 5.3+)

None
focus_trap bool | None

Trap keyboard focus inside the drawer

None
autofocus_selector str | None

CSS selector for the element to autofocus

None
**kwargs Any

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

{}

Returns:

Type Description
Div

FastHTML Div element with offcanvas structure

Source code in src/faststrap/components/navigation/drawer.py
@register(category="navigation", requires_js=True)
def Drawer(
    *children: Any,
    drawer_id: str | None = None,
    title: str | None = None,
    footer: Any | None = None,
    placement: PlacementType | None = None,
    backdrop: bool | None = None,
    scroll: bool | None = None,
    dark: bool | None = None,
    focus_trap: bool | None = None,
    autofocus_selector: str | None = None,
    header_cls: str | None = None,
    body_cls: str | None = None,
    footer_cls: str | None = None,
    title_cls: str | None = None,
    close_cls: str | None = None,
    **kwargs: Any,
) -> Div:
    """Bootstrap Offcanvas (Drawer) component for side panels and menus.

    Args:
        *children: Drawer body content
        drawer_id: Unique ID for the drawer (required for Bootstrap JS)
        title: Drawer header title
        footer: Drawer footer content
        placement: Drawer position (start=left, end=right, top, bottom)
        backdrop: Show backdrop overlay
        scroll: Allow body scroll when drawer is open
        dark: Use dark variant (Bootstrap 5.3+)
        focus_trap: Trap keyboard focus inside the drawer
        autofocus_selector: CSS selector for the element to autofocus
        **kwargs: Additional HTML attributes (cls, hx-*, data-*, etc.)

    Returns:
        FastHTML Div element with offcanvas structure
    """
    # Resolve API defaults
    cfg = resolve_defaults(
        "Drawer",
        placement=placement,
        backdrop=backdrop,
        scroll=scroll,
        dark=dark,
        focus_trap=focus_trap,
        autofocus_selector=autofocus_selector,
        header_cls=header_cls,
        body_cls=body_cls,
        footer_cls=footer_cls,
        title_cls=title_cls,
        close_cls=close_cls,
    )

    c_placement = cfg.get("placement", "start")
    c_backdrop = cfg.get("backdrop", True)
    c_scroll = cfg.get("scroll", False)
    c_dark = cfg.get("dark", False)
    c_focus_trap = cfg.get("focus_trap", False)
    c_autofocus_selector = cfg.get("autofocus_selector")
    c_header_cls = cfg.get("header_cls", "")
    c_body_cls = cfg.get("body_cls", "")
    c_footer_cls = cfg.get("footer_cls", "")
    c_title_cls = cfg.get("title_cls", "")
    c_close_cls = cfg.get("close_cls", "")

    # Ensure drawer id
    if drawer_id is None:
        from uuid import uuid4

        drawer_id = f"drawer-{uuid4().hex}"

    # Build offcanvas classes
    classes = ["offcanvas", f"offcanvas-{c_placement}"]

    if c_dark:
        classes.append("offcanvas-dark")

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

    # Prevent accidental conflict with our explicit id
    kwargs.pop("id", None)

    # Build attributes
    attrs: dict[str, Any] = {
        "cls": all_classes,
        "tabindex": "-1",
        "aria_labelledby": f"{drawer_id}Label",
    }

    # Configure backdrop and scroll
    if not c_backdrop:
        attrs["data_bs_backdrop"] = "false"
    if c_scroll:
        attrs["data_bs_scroll"] = "true"
    if c_focus_trap:
        attrs["data_fs_focus_trap"] = "true"
    if c_autofocus_selector:
        attrs["data_fs_autofocus"] = c_autofocus_selector

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

    # Build drawer structure
    parts = []

    # Header
    if title:
        header = Div(
            H5(title, cls=merge_classes("offcanvas-title", c_title_cls), id=f"{drawer_id}Label"),
            CloseButton(cls=c_close_cls, data_bs_dismiss="offcanvas"),
            cls=merge_classes("offcanvas-header", c_header_cls),
        )
        parts.append(header)

    # Body
    body = Div(*children, cls=merge_classes("offcanvas-body", c_body_cls))
    parts.append(body)

    # Footer (optional)
    if footer is not None:
        parts.append(Div(footer, cls=merge_classes("offcanvas-footer", c_footer_cls)))

    # Create the drawer with correct attrs including id
    return Div(*parts, id=drawer_id, **attrs)