Components
ResizablePanels
Panels side by side or stacked, with a line between two panels that the user drags or moves with the arrow keys to give one more room, such as a list next to its detail or a query over its results. Sizes are percents of the group.
Preview
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable-panels"
<ResizablePanelGroup className="h-64 w-full rounded-container border border-border">
<ResizablePanel defaultSize={35} minSize={20} className="p-md">
<p className="text-sm text-text-secondary">Inbox</p>
</ResizablePanel>
<ResizableHandle label="Resize inbox" />
<ResizablePanel className="p-md">
<p className="text-sm text-text-secondary">Message</p>
</ResizablePanel>
</ResizablePanelGroup>Usage
When to use
- Let someone give more room to one of two or three panels, such as a list next to its detail.
- Split an editor and its output, or a table and a preview, at a size the user picks.
When not to use
- The app's main navigation column. Use Sidebar.
- Content that overflows one box. Use ScrollArea.
- Switch between views in one place. Use Tabs.
Variants
orientation
Set on ResizablePanelGroup. horizontal puts the panels side by side, vertical stacks them.
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable-panels"
<ResizablePanelGroup className="h-64 w-full rounded-container border border-border">
<ResizablePanel defaultSize={35} minSize={20} className="p-md">
<p className="text-sm text-text-secondary">Inbox</p>
</ResizablePanel>
<ResizableHandle label="Resize inbox" />
<ResizablePanel className="p-md">
<p className="text-sm text-text-secondary">Message</p>
</ResizablePanel>
</ResizablePanelGroup>
<ResizablePanelGroup orientation="vertical" className="h-64 w-full rounded-container border border-border">
<ResizablePanel defaultSize={35} minSize={20} className="p-md">
<p className="text-sm text-text-secondary">Query</p>
</ResizablePanel>
<ResizableHandle label="Resize inbox" />
<ResizablePanel className="p-md">
<p className="text-sm text-text-secondary">Output</p>
</ResizablePanel>
</ResizablePanelGroup>Examples
List and detail
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable-panels"
<ResizablePanelGroup className="h-64 w-full rounded-container border border-border">
<ResizablePanel defaultSize={35} minSize={20} className="p-md">
<p className="text-sm text-text-secondary">Inbox</p>
</ResizablePanel>
<ResizableHandle label="Resize inbox" />
<ResizablePanel className="p-md">
<p className="text-sm text-text-secondary">Message</p>
</ResizablePanel>
</ResizablePanelGroup>Stacked, with a grip
A vertical group puts the panels one over the other; the grip shows where to grab.
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable-panels"
<ResizablePanelGroup orientation="vertical" className="h-72 w-full rounded-container border border-border">
<ResizablePanel defaultSize={60} className="p-md">
<p className="text-sm text-text-secondary">Query</p>
</ResizablePanel>
<ResizableHandle label="Resize output" withHandle />
<ResizablePanel className="p-md">
<p className="text-sm text-text-secondary">Output</p>
</ResizablePanel>
</ResizablePanelGroup>Three panels
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable-panels"
<ResizablePanelGroup className="h-64 w-full rounded-container border border-border">
<ResizablePanel defaultSize={25} minSize={15} className="p-md">
<p className="text-sm text-text-secondary">Folders</p>
</ResizablePanel>
<ResizableHandle label="Resize folders" />
<ResizablePanel className="p-md">
<p className="text-sm text-text-secondary">Files</p>
</ResizablePanel>
<ResizableHandle label="Resize files" />
<ResizablePanel defaultSize={30} minSize={20} className="p-md">
<p className="text-sm text-text-secondary">Preview</p>
</ResizablePanel>
</ResizablePanelGroup>Do and don't
Do
- Set minSize so no panel shrinks below what its content needs.
- Save the layout from onLayout and pass it back as defaultSize on the next visit.
- Below md, stack the content or show one panel at a time instead.
Don't
- Do not use for the app's main navigation column. Use Sidebar, which collapses.
- Do not put more than three panels in one group.
- Do not put a handle at the start or the end; a handle sits between two panels.
Props
| Name | Type | Values | Default | Description |
|---|---|---|---|---|
| orientation | enum | horizontal | vertical | horizontal | Set on ResizablePanelGroup. horizontal puts the panels side by side, vertical stacks them. |
| onLayout | (sizes: number[]) => void | - | - | Set on ResizablePanelGroup. Called with every panel's size in percent after a drag or a key press, to save the layout. |
| keyboardStep | number | - | 5 | Set on ResizablePanelGroup. Percent a handle moves for each arrow key press. |
| defaultSize | number | - | - | Set on ResizablePanel. Starting size in percent. Panels without one share what is left. |
| minSize | number | - | 10 | Set on ResizablePanel. Smallest size in percent. |
| maxSize | number | - | 90 | Set on ResizablePanel. Largest size in percent. |
| label required | string | - | - | Set on ResizableHandle. Names the handle, such as Resize sidebar. |
| withHandle | boolean | - | false | Set on ResizableHandle. Shows a small grip tile on the line, for a handle that is hard to find. |
Slots
| Name | Required | Description |
|---|---|---|
| panels | no | Children of ResizablePanelGroup: ResizablePanel and ResizableHandle, a handle between each two panels, as direct children. |
Anatomy
| Part | Description |
|---|---|
| root | The group: a flex row or column that fills its container. Give it a height when it is horizontal. |
| panel | One panel. It grows by its share and scrolls on its own when the content is larger. |
| handle | The 1px line between two panels, focusable and draggable. |
| hitArea | The transparent ::before across the line, size-target-min wide. |
| grip | The optional small tile on the line that shows where to grab. |
| icon | The grip icon in the tile, 16px. |
Accessibility
Role: separator
| Keys | Action |
|---|---|
| Tab | Moves focus to the next handle. |
| ArrowLeft / ArrowRight | In a horizontal group, makes the panel before the handle smaller or larger by keyboardStep percent. |
| ArrowUp / ArrowDown | In a vertical group, makes the panel above the handle smaller or larger by keyboardStep percent. |
| Home / End | Makes the panel before the handle as small or as large as its limits allow. |
- Each handle is a focusable separator named by label, such as EN `Resize sidebar`. It controls the panel before it (aria-controls).
- aria-valuenow is the size of the panel before the handle in percent; aria-valuemin and aria-valuemax are its limits, given the next panel's limits too.
- A horizontal group has vertical separators (aria-orientation="vertical") and the reverse.
- In a right-to-left page the arrow keys and the drag follow the reading direction.
- The 1px line has a hit area of size-target-min across it, so it is easy to grab with a finger.
Install
npx werewolf-ui add resizable-panelsRun npx werewolf-ui init once in your app before adding components. Init sets up tokens, their CSS import and agent rules. Add installs this component and its dependencies while preserving your theme. See Installation for requirements and the alternative registry setup.