Resizable
Split a surface into panels the user can drag to resize. No Radix primitive ships this, so the handle is hand-rolled: it carries role=separator, is arrow-key operable, and reports through aria-valuenow.
Installation#
Usage#
import {
ResizablePanelGroup,
ResizablePanel,
ResizableHandle,
} from "@/components/ui/resizable"
export function Example() {
return (
<ResizablePanelGroup direction="horizontal" className="h-64 rounded-lg border">
<ResizablePanel defaultSize={60}>One</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={40}>Two</ResizablePanel>
</ResizablePanelGroup>
)
}Horizontal#
The default direction lays panels side-by-side. Give each panel a defaultSize weight (conventionally summing to 100), then drag the divider to redistribute the space between the two panels it sits between.
Vertical#
Set direction="vertical" to stack panels and drag a horizontal divider. The handle, cursor, and keyboard axis all flip automatically.
With handle#
Add withHandle to render a minimal, fully-rounded grip pill: a clear "drag me" affordance for wide gutters. Without it the divider is a hairline that highlights on hover and focus.
Custom handle#
Pass children to a ResizableHandle to swap the visual entirely (an icon nub, a brand chip, your own shape) while keeping all the drag, keyboard, and ARIA behavior. The two examples below show a dotted chip and a bold brand pill.
Min & max sizes#
Clamp how far a panel can be dragged with minSize and maxSize. A number (or "30%") is a proportional weight; a pixel string like "240px" is a hard bound pinned as a real CSS min/max-width, so it holds at any group size: drag it, then shrink your window and the sidebar below keeps its 240px–360px range instead of scaling away.
Collapsible#
Mark a panel collapsible and it snaps shut once dragged past half its minSize, settling at collapsedSize (default 0): the pattern for a sidebar that tucks away. Drag back out to restore it.
Nested dashboard#
Groups nest: put a vertical ResizablePanelGroup inside a panel of a horizontal one to build a real dashboard split. Each group resizes independently.
API reference#
ResizablePanelGroup
The split container. Forwards every div prop.
direction:"horizontal" | "vertical". Lay panels side-by-side or stacked. Default"horizontal".onLayout:(sizes: number[]) => void. Fires with the panel weights (DOM order) whenever the layout changes.className: sizes the box; panels fill it. Give it a height (and usually a border/radius).
ResizablePanel
One region of the split. Forwards every div prop.
defaultSize:number. Starting weight (conventionally a percentage). Omit on every panel for an equal split.minSize/maxSize:number | string. Clamp the drag range. A number/"%"is a proportional weight;"240px"is a hard width/height bound that holds on window resize.collapsible:boolean. Snap shut past halfminSize.collapsedSize:number | string. Size when collapsed. Default0.asChild:boolean. Render as the child element via Radix Slot.
ResizableHandle
The draggable divider. role="separator", arrow-key operable (Shift = coarse, Home/End jump to min/max). Forwards every div prop.
withHandle:boolean. Show the minimal rounded grip pill.children:ReactNode. Custom handle content, centered on the divider, replaces the default pill while keeping all behavior.disabled:boolean. Disable dragging and keyboard resizing.