Panels separated by draggable handles — resize by mouse, touch or keyboard.
One
Two
import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@/components/ui/resizable'
export default function ResizableDemo() {
return (
<ResizablePanelGroup orientation="horizontal" className="h-[200px] max-w-md rounded-lg border border-border">
<ResizablePanel defaultSize="50%" minSize="20%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">One</span>
</div>
</ResizablePanel>
<ResizableHandle withHandle aria-label="Resize panels" />
<ResizablePanel defaultSize="50%" minSize="20%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Two</span>
</div>
</ResizablePanel>
</ResizablePanelGroup>
)
}
Installation
pnpm dlx shadcn@latest add https://kagadmodyaa.vercel.app/r/resizable.jsonNeeds a React + Tailwind project set up with shadcn. Copies the component source straight into your project, so you can edit it.
Resizable imports cn() from lib/utils.ts — add it too if you don't have it yet:
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
Usage
import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@/components/ui/resizable'
<ResizablePanelGroup orientation="horizontal" className="h-[200px] rounded-lg border">
<ResizablePanel defaultSize="50%" minSize="20%">One</ResizablePanel>
<ResizableHandle withHandle aria-label="Resize panels" />
<ResizablePanel defaultSize="50%">Two</ResizablePanel>
</ResizablePanelGroup>
// Sizes: strings are percentages ("30%"), numbers are pixels (240).
// The group needs a height (or width, for a vertical one) — e.g. h-[200px].Examples
Vertical
Header
Content
import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@/components/ui/resizable'
export default function ResizableVertical() {
return (
<ResizablePanelGroup orientation="vertical" className="h-[280px] max-w-md rounded-lg border border-border">
<ResizablePanel defaultSize="30%" minSize="15%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Header</span>
</div>
</ResizablePanel>
<ResizableHandle aria-label="Resize header" />
<ResizablePanel defaultSize="70%" minSize="15%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Content</span>
</div>
</ResizablePanel>
</ResizablePanelGroup>
)
}
Nested groups
Sidebar
Editor
Terminal
import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@/components/ui/resizable'
export default function ResizableNested() {
return (
<ResizablePanelGroup orientation="horizontal" className="h-[320px] max-w-lg rounded-lg border border-border">
<ResizablePanel defaultSize="30%" minSize="15%" maxSize="50%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Sidebar</span>
</div>
</ResizablePanel>
<ResizableHandle withHandle aria-label="Resize sidebar" />
<ResizablePanel defaultSize="70%">
<ResizablePanelGroup orientation="vertical">
<ResizablePanel defaultSize="40%" minSize="15%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Editor</span>
</div>
</ResizablePanel>
<ResizableHandle withHandle aria-label="Resize editor" />
<ResizablePanel defaultSize="60%" minSize="15%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Terminal</span>
</div>
</ResizablePanel>
</ResizablePanelGroup>
</ResizablePanel>
</ResizablePanelGroup>
)
}
API Reference
| Name | Type | Default | Description |
|---|---|---|---|
| orientation | 'horizontal' | 'vertical' | 'horizontal' | Set on ResizablePanelGroup. Horizontal puts panels side by side; vertical stacks them. Groups can be nested. |
| defaultSize | number | string | — | Initial size of a ResizablePanel. Write percentages as strings ("30%") and pixels as numbers (240). Panels without one share the rest equally. |
| minSize / maxSize | number | string | '0%' / '100%' | Limits for a panel, in the same units as defaultSize (also "20rem", "50vw"). |
| collapsible / collapsedSize | boolean / number | string | false / 0% | Lets a panel snap shut when dragged below minSize. |
| onLayoutChange / onLayoutChanged | (layout: Record<string, number>) => void | — | Set on the group. The first fires while dragging, the second once the pointer is released — use it to save the layout. |
| defaultLayout | Record<string, number> | — | Restore a saved layout on the group (keyed by panel id). |
| ResizableHandle withHandle | boolean | false | Adds a small grip in the middle of the handle. |
| ResizableHandle aria-label | string | — | Names the handle for screen readers, e.g. "Resize sidebar". |
| disabled | boolean | false | On a group, panel or handle: stops it from being resized. |
| Keyboard | — | — | Focus a handle with Tab. The arrow keys along its axis move it by 5%, Home and End send it to the limits, Enter collapses or restores a collapsible panel, and F6 jumps between handles. |