A generic two-column layout: a left pane and a right pane, optionally user-resizable by
dragging a handle between them. It's a positioning primitive rather than a single
canonical M3 pattern — three anchor modes cover the different
places a split layout can live on a page.
M3's closest canonical layout is list-detail, but that pattern carries specific list/detail semantics (a selectable list driving a
detail view). SplitPane here is deliberately more general —
a resizable two-column primitive with no opinion about what goes in either side. This
library's own root Rail layout and the docs secondary nav (this
very sidebar's parent structure) are both built on it, alongside more list-detail-shaped uses.
anchor picks how the left pane is positioned. Getting this wrong
under nested layouts is the most common mistake — pick based on what already surrounds the split,
not just what "looks right" in isolation.
viewport (default)
The left pane fixes to the true browser edge (position: fixed). Use this when SplitPane is the outermost / only positioned element on the page —
e.g. this library's root Rail layout.
parent
The left pane is absolutely positioned within a relative ancestor. Good for embedded/contained demos (a bounded sandbox box) — it does not stay
pinned while the page itself scrolls.
sticky
Left and right sit in normal flex flow and the left pane sticks (position: sticky) as the page scrolls. Use this for a sidebar nested inside content that is already
offset by something else — e.g. the docs secondary component-list nav, which lives
inside the root layout's Rail-offset content column. viewport would fix it to the true left edge (underneath/behind the Rail); parent would lose the "stays visible while scrolling" behavior. Pair with resizable={false} for a static (non-draggable) sidebar.
import { SplitPane } from '@noxlovette/material'; See it live, with Controls, in Storybook rather than a hand-rolled preview here. Separate
stories demo the viewport and sticky anchor modes inside a bounded canvas.
Playground
Open the SplitPane story — includes Anchor Viewport and Anchor Sticky variants.
A resizable split, anchored to the viewport:
<script lang="ts">
import { SplitPane } from '@noxlovette/material';
</script>
<SplitPane anchor="viewport" leftWidth={320} minLeft={240} maxLeft={480}>
{#snippet left()}
<nav>...</nav>
{/snippet}
{#snippet right()}
<main>...</main>
{/snippet}
</SplitPane> A static, non-draggable sidebar nested inside already-offset content:
<SplitPane anchor="sticky" resizable={false} leftWidth={240}>
{#snippet left()}
<nav>...</nav>
{/snippet}
{#snippet right()}
<main>...</main>
{/snippet}
</SplitPane> | Prop | Type | Default | Description |
|---|---|---|---|
left required | Snippet | — | Content for the left pane. |
right required | Snippet | — | Content for the right pane. |
anchor | "viewport" | "parent" | "sticky" | "viewport" | Positioning strategy for the left pane — see Anchor Modes below. |
resizable | boolean | true | Whether the split is user-draggable. Set false for a static sidebar — hides the drag handle and skips width persistence. |
leftWidth | number | 396 | Initial left pane width in px. Bindable. |
minLeft | number | 280 | Minimum left pane width in px, enforced while dragging. |
maxLeft | number | 720 | Maximum left pane width in px, enforced while dragging. |
storageKey | string | "splitpane:leftWidth" | localStorage key used to persist the dragged width. |
persist | boolean | true | Whether to persist and restore the width from localStorage. Ignored when resizable is false. |
onPaddingChange | (padding: number) => void | — | Called on mount/resize/destroy with leftWidth + 80px, letting a parent adjust footer padding to avoid the fixed left pane overlapping page-end content. |
mobilePane | "left" | "right" | "right" | Which pane is shown below the md breakpoint; both are always visible on desktop. |
full | boolean | true | Whether the panes stretch to fill available height. |
rounded | boolean | true | Rounds the top/bottom corners of the panes on md+ breakpoints. |
leftClass | string | — | Extra class applied to the left pane element. |
rightClass | string | — | Extra class applied to the right pane element. |
Resize handle semantics
The drag handle renders role="separator" aria-orientation="vertical" with aria-valuenow/min/max reflecting the current and clamped width range.
Desktop-only split
The left pane and handle are hidden below the md breakpoint (hidden md:block); on mobile only one pane is shown at a time, chosen by mobilePane.