Resizable panels
Trade space between panes by dragging the divider between them.
split viewresizablepanes
Preview
Resizable panels preview
Live · keyboard ready
Files
Editor
When to use
- Editor, mail, or dashboard layouts where people trade space between side-by-side panes.
- Sidebars that should collapse by dragging or flicking toward the edge.
- Layouts whose pane sizes should persist between visits via onLayoutChange.
When not to use
- Use drawer for a panel that slides over the page.
- Use tabs when only one pane needs to be visible at a time.
- Avoid it for vertical splits, since it is horizontal only.
shadcn CLI
Also installs (1)
- manicat-foundation
Manual install(+)
registry.jsonjson
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "resizable-panels",
"type": "registry:ui",
"title": "Resizable panels",
"description": "Trade space between panes by dragging the divider between them.",
"categories": [
"disclosure"
],
"dependencies": [
"lucide-react",
"motion"
],
"registryDependencies": [
"https://manicat.viraza.net/r/manicat-foundation.json"
],
"files": [
{
"path": "registry/components/resizable-panels/resizable-panels.tsx",
"type": "registry:component",
"target": "@components/manicat/resizable-panels/resizable-panels.tsx"
},
{
"path": "registry/components/resizable-panels/resizable-panels.module.css",
"type": "registry:component",
"target": "@components/manicat/resizable-panels/resizable-panels.module.css"
}
],
"docs": "Docs and live preview: https://uiarc.dev/components/resizable-panels",
"meta": {
"tier": "free",
"kind": "component",
"docs": "https://manicat.viraza.net/components/resizable-panels",
"markdown": "https://uiarc.dev/components/resizable-panels/markdown",
"tags": [
"split view",
"resizable",
"panes"
]
}
}Usage
Install the component, then copy this into your app. Change the content and the props to fit your screen.
example.tsxtsx
"use client";
import { ResizablePanel, ResizablePanels } from "@/components/manicat/resizable-panels/resizable-panels";
export function ResizablePanelsExample() {
return (
<div className="h-48 w-full max-w-2xl">
<ResizablePanels label="Editor split" className="rounded-[var(--radius-panel)] border border-(color:--border)">
<ResizablePanel id="files" label="Files" defaultSize={40}>
<div className="p-4 text-sm text-(color:--text-muted)">Files</div>
</ResizablePanel>
<ResizablePanel id="editor" label="Editor" defaultSize={60}>
<div className="p-4 text-sm text-(color:--text-muted)">Editor</div>
</ResizablePanel>
</ResizablePanels>
</div>
);
}
Examples
Real screens rather than isolated props. Switch to Code to copy a whole example into your project.
Editor split Two resizable panes, like a file tree next to an editor. Drag the divider or use the arrow keys.
Editor split
Two resizable panes, like a file tree next to an editor. Drag the divider or use the arrow keys. preview
Live · keyboard ready
app/ · lib/ · site/
export default function Page() { … }
editor-split-two-resizable-panes-like-a-file-tree-next-to-an-editor-drag-the-divider-or-use-the-arrow-keys-.tsxtsx
"use client";
import { ResizablePanel, ResizablePanels } from "@/components/manicat/resizable-panels/resizable-panels";
export function ResizablePanelsEditorExample() {
return (
<ResizablePanels label="Editor split" className="h-48 w-full max-w-2xl rounded-[var(--radius-panel)] border border-(color:--border)">
<ResizablePanel id="files" label="Files" defaultSize={35}>
<div className="p-4 text-sm text-(color:--text-muted)">app/ · lib/ · site/</div>
</ResizablePanel>
<ResizablePanel id="editor" label="Editor" defaultSize={65}>
<div className="p-4 font-mono text-sm text-(color:--foreground)">export default function Page() {"{ … }"}</div>
</ResizablePanel>
</ResizablePanels>
);
}
Keyboard interactions
| Keys | Action |
|---|---|
| ArrowLeft / ArrowRight | Moves the focused divider 16px; with Shift, 64px. Past a limit, collapses a collapsible pane. |
| Home / End | Moves the divider to its minimum or maximum. |
| Enter | Collapses or restores a collapsible neighbouring pane. |
| Escape | During a drag, returns the divider to where it started. |
Accessibility
- Each divider is a focusable role="separator" with aria-orientation, aria-controls, aria-valuenow, min, and max in percent.
- aria-valuetext announces the share, or that a pane is hidden and Enter shows it.
- Collapsed panes are inert. The group has role="group"; pass label to name it.
Motion
- Dividers follow the pointer 1:1, rubber-band past limits, and spring to rest; flicks toward an edge collapse a pane. Double-click resets the layout.
- Pane content fades as it is squeezed below its minimum and a restore tab pops in on a snappy spring.
- Reduced motion jumps panes to their targets and fades the restore tab.
Responsive behavior
- Panes do not stack on narrow screens; on mobile, render a single pane or switch to tabs yourself.
- Dividers use pointer capture with touch-action none, so touch, pen, and mouse drags all work.
- The group needs a definite height from its parent.
Performance
- A ResizeObserver on the group refits pane widths when the container resizes.
- Dragging sets widths directly and springs only on release or collapse; pane content is never re-rendered by the drag itself.
Notes for AI
- Use for editor, mail, or dashboard layouts where people trade space between side-by-side panes. Horizontal only.
- Give the parent a definite height and persist onLayoutChange shares to restore via defaultSize.