Components
Accessible resizable panel groups and layouts with keyboard support.
Playground
orientation, ResizablePanel wraps each region, and ResizableHandle draws the draggable divider with the border token. Groups nest — place a group inside a panel to split it along the other axis. Sizes are strings for percentages ("50%") and numbers for pixels.import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@workspace/ui/components/resizable"
<ResizablePanelGroup
orientation="horizontal"
className="max-w-md rounded-lg border"
>
<ResizablePanel defaultSize="50%">
<div className="flex h-[200px] items-center justify-center p-6">
<span className="font-semibold">One</span>
</div>
</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize="50%">
<ResizablePanelGroup orientation="vertical">
<ResizablePanel defaultSize="25%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Two</span>
</div>
</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize="75%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Three</span>
</div>
</ResizablePanel>
</ResizablePanelGroup>
</ResizablePanel>
</ResizablePanelGroup>orientation="vertical" to stack panels top to bottom — the group switches to a column layout and the handle becomes a horizontal divider. Size the group with a min-height — the primitive sets its own inline height: 100%, so a height utility on className is overridden.<ResizablePanelGroup
orientation="vertical"
className="min-h-[200px] max-w-md rounded-lg border"
>
<ResizablePanel defaultSize="25%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Header</span>
</div>
</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize="75%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Content</span>
</div>
</ResizablePanel>
</ResizablePanelGroup>withHandle to render a small grip on top of it — a rounded pill in the border token that rotates with the orientation — making the affordance visible instead of discovered on hover.<ResizablePanelGroup
orientation="horizontal"
className="min-h-[200px] max-w-md rounded-lg border"
>
<ResizablePanel defaultSize="25%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Sidebar</span>
</div>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize="75%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Content</span>
</div>
</ResizablePanel>
</ResizablePanelGroup>collapsible snaps to its collapsedSize when dragged below minSize — the classic icon-rail sidebar. Mixing units works: percentage strings for the fluid sizes, a pixel number for the fixed collapsed rail.<ResizablePanelGroup
orientation="horizontal"
className="min-h-[200px] max-w-md rounded-lg border"
>
<ResizablePanel
defaultSize="30%"
minSize="20%"
collapsible
collapsedSize={48}
>
<div className="flex h-full items-center justify-center overflow-hidden p-6">
<span className="truncate font-semibold">Sidebar</span>
</div>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize="70%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Content</span>
</div>
</ResizablePanel>
</ResizablePanelGroup>role="separator" that reports the panel size through aria-valuenow. Tab reaches each handle and shows the ring token as its focus indicator; arrow keys resize the adjacent panels, Home and End jump to the minimum and maximum, and Enter toggles a collapsible panel. Double-clicking a handle resets the panels to their default sizes — opt out with disableDoubleClick.// keyboard users resize with the handle focused —
// Tab to the separator, then Arrow keys, Home/End, Enter (collapse)
<ResizablePanelGroup orientation="horizontal">
<ResizablePanel collapsible collapsedSize={48}>{/* … */}</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel>{/* … */}</ResizablePanel>
</ResizablePanelGroup>"25%") or a pixel number. The handle styles with logical properties, so RTL — via the dir attribute or an ancestor — mirrors the layout correctly.| Part | Prop | Default |
|---|---|---|
| ResizablePanelGroup | orientation?: "horizontal" | "vertical" | "horizontal" |
| ResizablePanelGroup | onLayoutChanged?: (layout, meta) => void | — |
| ResizablePanel | defaultSize?: number | string | auto |
| ResizablePanel | minSize? / maxSize?: number | string | — |
| ResizablePanel | collapsible?: boolean | false |
| ResizablePanel | collapsedSize?: number | string | 0 |
| ResizablePanel | onResize?: (size, id, prev) => void | — |
| ResizableHandle | withHandle?: boolean | false |
| ResizableHandle | disabled?: boolean | false |
| ResizableHandle | disableDoubleClick?: boolean | false |
Tokens used
5 design tokens consumed by the Resizable component.
--border
Border
--ring
Ring
--background
Background
--radius
Radius
--spacing
Spacing unit (4px)