Resizable
Split-pane layouts with draggable dividers.
Overview
Split panes built on react-resizable-panels
(v4): pointer and keyboard resizing, nested groups, collapsible panels and
layout persistence come from the primitive. The divider is an outline-variant
hairline with an invisible 8px grab zone straddling it — the seam stays quiet,
the target stays comfortable. It picks up outline on hover and fills primary
while it is dragged or focused, and withHandle puts the MD3 drag-handle pill
on the seam.
$ npx morpha-ui add resizableComposition
<ResizablePanelGroup orientation="horizontal">
<ResizablePanel defaultSize="50%" />
<ResizableHandle withHandle />
<ResizablePanel defaultSize="50%" />
</ResizablePanelGroup>Sizes are percentages when given as strings ("50%", "50"), pixels when given
as numbers, and any CSS unit otherwise ("16rem", "50vh"). ResizablePanel
applies its className to an inner element, so you can lay out the pane's
content directly on it.
The group sets height: 100% on itself inline, so a height class on
ResizablePanelGroup has no effect — it fills its container instead. Put the
height (and the border and rounding) on a wrapper around the group, as every
example below does.
Examples
Vertical
orientation="vertical" stacks the panes and turns the divider — and its pill —
with them.
Handle
withHandle renders the drag-handle pill: a 4×32px outline-variant grip that
goes primary on drag and on keyboard focus. Use it whenever the divider is a
primary affordance rather than a passive seam.
Collapsible
A collapsible panel snaps shut when it is dragged below its minSize, down to
collapsedSize. panelRef exposes the imperative API — collapse(),
expand(), resize(), isCollapsed() — for driving it from a button.
Layout
Groups nest, and each panel carries its own minSize / maxSize, so a three-pane
app shell can never squeeze a pane shut by accident.
Drag either divider to rebalance the three panes. Each one keeps its own minimum width, so the reading pane never gets squeezed shut.
RTL
The group follows the document direction: under dir="rtl" the panes and the
drag axis mirror, with no extra configuration.
Persisting the layout
onLayoutChanged fires once a drag settles — it is the callback to persist —
and defaultLayout takes the stored map of panel id to percentage back. Give
every panel a stable id so the layout can be matched up again.
<ResizablePanelGroup
orientation="horizontal"
defaultLayout={savedLayout}
onLayoutChanged={(layout) => {
localStorage.setItem('layout', JSON.stringify(layout))
}}
>
<ResizablePanel id="sidebar" defaultSize="30%" />
<ResizableHandle withHandle />
<ResizablePanel id="content" defaultSize="70%" />
</ResizablePanelGroup>Installation
Morpha UI components are distributed via the Morpha CLI: npx morpha-ui add resizable. In the meantime, since this is the base shadcn component, you
can also add it directly:
npx shadcn@latest add resizableAPI Reference
ResizablePanelGroup
Prop
Type
ResizablePanel
Prop
Type
ResizableHandle
Prop
Type
Accessibility
- Dividers implement the WAI-ARIA window-splitter pattern: focus with Tab, resize with the Arrow keys, Home/End to collapse and expand.
- Keyboard focus draws the kit's
secondaryring on the divider and fills the pill withprimary. - The grab zone is 8px wide regardless of the 1px hairline, and the primitive widens the hit target further on coarse (touch) pointers.
- Double-clicking a divider restores the neighbouring panels to their default sizes; pass
disableDoubleClickto opt out.