Skip to content

SplitPane

@experimental

Two-pane resizable layout for master/detail screens, documentation editors, code inspectors, productivity apps, and exam question banks.


Quick Start

from faststrap.components.layout import SplitPane
from faststrap import Card, ListGroup, ListGroupItem

SplitPane(
    left=Card(
        ListGroup(
            ListGroupItem("Item 1", active=True),
            ListGroupItem("Item 2"),
            ListGroupItem("Item 3"),
        ),
        header="Items",
    ),
    right=Card("Select an item to view details.", header="Detail"),
    initial_ratio="30/70",
)

Features

  • Resizable divider (drag to adjust pane ratio)
  • Collapsible left pane with toggle button
  • Responsive: stacks vertically on mobile
  • Configurable initial ratio
  • Works with any FastHTML/Faststrap content in either pane
  • Minimal JavaScript for drag-to-resize (progressive enhancement)

Parameters

Parameter Type Default Description
left Any required Left/master pane content
right Any required Right/detail pane content
initial_ratio str "30/70" Pane width ratio, e.g. "30/70" or "40/60"
collapsible bool False Show collapse toggle for left pane
collapsed bool False Start with left pane collapsed
divider_width str "4px" Width of the draggable divider
min_left str "200px" Minimum left pane width
max_left str "50%" Maximum left pane width
stack_on "sm" \| "md" \| "lg" \| "xl" \| "xxl" \| None "md" Breakpoint to stack vertically
**kwargs Any Extra wrapper attributes

Usage Examples

Exam Editor

from faststrap import SplitPane, Card, DataTable, Form

SplitPane(
    left=Card(
        DataTable(questions_df, page_size=20),
        header="Question Bank",
    ),
    right=Card(
        Form(
            FormGroup("Question", Input(name="question")),
            FormGroup("Answer", Input(name="answer")),
        ),
        header="Editor",
    ),
    initial_ratio="40/60",
    collapsible=True,
)

Documentation Editor

from faststrap import Markdown, SplitPane

SplitPane(
    left=Markdown(doc_text),
    right=Card("Inspector"),
    initial_ratio="2/1",
    stack_on="lg",
)

Notes

  • Requires minimal JavaScript for the drag-to-resize interaction (progressive enhancement).
  • On mobile, panes stack vertically automatically.
  • Marked @experimental.

API Reference

faststrap.components.layout.split_pane.SplitPane(left, right, *, initial_ratio='30/70', collapsible=False, collapsed=False, divider_width='4px', min_left='200px', max_left='50%', stack_on='md', **kwargs)

Two-pane resizable layout for master/detail, editors, and dashboards.

Minimal JavaScript for drag-to-resize. Stacks vertically on mobile.

Parameters:

Name Type Description Default
left Any

Left/master pane content.

required
right Any

Right/detail pane content.

required
initial_ratio str

CSS grid-template-columns or flex ratio string for the initial pane widths. Use "30/70" for 30/70 split.

'30/70'
collapsible bool

Show a collapse toggle for the left pane.

False
collapsed bool

Start with the left pane collapsed.

False
divider_width str

Width of the draggable divider.

'4px'
min_left str

Minimum width for the left pane.

'200px'
max_left str

Maximum width for the left pane.

'50%'
stack_on str | None

Bootstrap breakpoint at which panes stack vertically. Pass None to disable stacking.

'md'
**kwargs Any

Additional HTML attributes for the wrapper.

{}

Returns:

Type Description
Div

FastHTML Div element with resizable split-pane layout.

Source code in src/faststrap/components/layout/split_pane.py
@register(category="layout", requires_js=True)
@experimental
def SplitPane(
    left: Any,
    right: Any,
    *,
    initial_ratio: str = "30/70",
    collapsible: bool = False,
    collapsed: bool = False,
    divider_width: str = "4px",
    min_left: str = "200px",
    max_left: str = "50%",
    stack_on: str | None = "md",
    **kwargs: Any,
) -> Div:
    """Two-pane resizable layout for master/detail, editors, and dashboards.

    Minimal JavaScript for drag-to-resize. Stacks vertically on mobile.

    Args:
        left: Left/master pane content.
        right: Right/detail pane content.
        initial_ratio: CSS ``grid-template-columns`` or flex ratio string
            for the initial pane widths. Use ``"30/70"`` for 30/70 split.
        collapsible: Show a collapse toggle for the left pane.
        collapsed: Start with the left pane collapsed.
        divider_width: Width of the draggable divider.
        min_left: Minimum width for the left pane.
        max_left: Maximum width for the left pane.
        stack_on: Bootstrap breakpoint at which panes stack vertically.
            Pass ``None`` to disable stacking.
        **kwargs: Additional HTML attributes for the wrapper.

    Returns:
        FastHTML ``Div`` element with resizable split-pane layout.
    """
    stack_on = _normalize_breakpoint(stack_on)

    user_cls = kwargs.pop("cls", "")
    classes = ["faststrap-split-pane"]
    if stack_on:
        classes.append(f"flex-{stack_on}-row")
        classes.append("flex-column")
    else:
        classes.append("d-flex")
        classes.append("flex-row")

    attrs: dict[str, Any] = {
        "cls": merge_classes(" ".join(classes), user_cls),
        "data_fs_split_min_left": min_left,
        "data_fs_split_max_left": max_left,
        "data_fs_split_divider_width": divider_width,
    }
    if initial_ratio:
        attrs["data_fs_split_ratio"] = initial_ratio
    if collapsible:
        attrs["data_fs_split_collapsible"] = "true"
    if collapsed:
        attrs["data_fs_split_collapsed"] = "true"
    attrs.update(convert_attrs(kwargs))

    left_style: dict[str, str] = {}
    right_style: dict[str, str] = {}
    if initial_ratio:
        parts = initial_ratio.split("/")
        if len(parts) == 2:
            left_style["width"] = f"calc({parts[0].strip()}% - {divider_width})"
            left_style["flex"] = f"0 0 calc({parts[0].strip()}% - {divider_width})"
            right_style["width"] = f"calc({parts[1].strip()}% - {divider_width})"
            right_style["flex"] = f"0 0 calc({parts[1].strip()}% - {divider_width})"

    left_cls = merge_classes("faststrap-split-pane-left", "")
    right_cls = merge_classes("faststrap-split-pane-right", "")

    left_attrs: dict[str, Any] = {"cls": left_cls}
    if left_style:
        left_attrs["style"] = left_style

    right_attrs: dict[str, Any] = {"cls": right_cls}
    if right_style:
        right_attrs["style"] = right_style

    divider = Div(
        "",
        cls="faststrap-split-pane-divider",
        style=f"width: {divider_width}; background: transparent;",
    )

    children = [
        Div(left, **left_attrs),
        divider,
        Div(right, **right_attrs),
    ]

    style_css = Style(_SPLITPANE_CSS)
    init_js = Script(NotStr(_SPLITPANE_JS), defer_=True)

    return Div(
        style_css,
        init_js,
        Div(*children, **attrs),
    )