TL;DR: React Resizable Panels lets users drag to resize split-view layouts. Use
PanelGroup,Panel, andPanelResizeHandlecomponents. Supports collapsible panels,autoSaveIdfor persistent sizing, and full keyboard accessibility. ~8kB. Official docs: react-resizable-panels.vercel.app.
What is React Resizable Panels?
React Resizable Panels provides <PanelGroup>, <Panel>, and <PanelResizeHandle> components for building split-view layouts where the user can drag to resize sections. It's used in shadcn/ui's ResizablePanelGroup and is the standard for IDE-style split layouts in React.
The mental model is simple. A PanelGroup sets a direction, either horizontal or vertical. Inside it, Panel elements hold your content, and PanelResizeHandle elements sit between them as draggable dividers. Sizes are expressed as percentages of the group, not pixels, which is what keeps layouts fluid when the window resizes. The library does the math so two panels always add up to the full space, even mid-drag.
Why does that percentage model matter? Because pixel-based splitters break the moment the viewport changes. A sidebar fixed at 280px looks fine on a laptop and absurd on a 27-inch monitor. Percentage panels scale with the screen, and minimum and maximum bounds keep them from collapsing into something useless. It's a small design decision with a big payoff in how the layout holds up across devices.
Where split layouts earn their keep
Reach for it when building code editors and IDEs, where you want side panel, editor, and terminal splits. It fits admin dashboards too, pairing a sidebar with a content area whose width the user controls. Document viewers benefit from a file tree beside a preview pane, and email clients are the classic three-column case: folder list, message list, and message detail, each independently resizable.
The common thread is power users who spend hours in one screen. Those users have strong opinions about how much room each region gets, and a draggable splitter lets them tune the layout to their own workflow. Pair that with persistent sizing and the app remembers their preference forever, which is a quietly delightful touch that most teams forget to add.
Key Features
- Horizontal and vertical splits -
direction="horizontal"or"vertical" - Nested groups - panels can contain other
PanelGroupcomponents - Collapsible panels -
collapsible+minSizefor panels that snap closed - Persistent sizing -
autoSaveIdsaves panel sizes tolocalStorage - Keyboard accessible - resize handles support arrow keys
- Imperative API -
panelRef.collapse()/panelRef.expand()/panelRef.resize()
Installation
npm install react-resizable-panels
Basic Usage
import { PanelGroup, Panel, PanelResizeHandle } from 'react-resizable-panels'
function SplitEditor() {
return (
<PanelGroup direction="horizontal" className="h-screen">
{/* Sidebar */}
<Panel defaultSize={20} minSize={15} maxSize={40} className="bg-slate-900">
<FileTree />
</Panel>
<PanelResizeHandle className="w-1 bg-slate-700 hover:bg-indigo-500 transition-colors cursor-col-resize" />
{/* Editor */}
<Panel defaultSize={50} className="bg-slate-800">
<CodeEditor />
</Panel>
<PanelResizeHandle className="w-1 bg-slate-700 hover:bg-indigo-500 transition-colors cursor-col-resize" />
{/* Terminal */}
<Panel defaultSize={30} minSize={10} className="bg-slate-900">
<Terminal />
</Panel>
</PanelGroup>
)
}
Collapsible Panel with Toggle Button
import { Panel, PanelResizeHandle, type ImperativePanelHandle } from 'react-resizable-panels'
import { useRef } from 'react'
function CollapsibleSidebar() {
const sidebarRef = useRef<ImperativePanelHandle>(null)
function toggleSidebar() {
const panel = sidebarRef.current
if (!panel) return
panel.isCollapsed() ? panel.expand() : panel.collapse()
}
return (
<>
<button onClick={toggleSidebar}>Toggle sidebar</button>
<PanelGroup direction="horizontal">
<Panel ref={sidebarRef} collapsible defaultSize={25} minSize={15}>
<Sidebar />
</Panel>
<PanelResizeHandle />
<Panel>
<Content />
</Panel>
</PanelGroup>
</>
)
}
Persistent Sizing
// Sizes are automatically saved to localStorage under the key "main-layout"
<PanelGroup direction="horizontal" autoSaveId="main-layout">
<Panel defaultSize={30}>...</Panel>
<PanelResizeHandle />
<Panel>...</Panel>
</PanelGroup>
TypeScript Tips
Use ImperativePanelHandle for the ref type:
import { type ImperativePanelHandle } from 'react-resizable-panels'
const ref = useRef<ImperativePanelHandle>(null)
// Available methods:
ref.current?.collapse()
ref.current?.expand()
ref.current?.resize(40) // resize to 40%
ref.current?.getSize() // get current size in %
ref.current?.isCollapsed() // boolean
Keyboard access and accessibility
A draggable divider is useless to anyone who can't use a mouse, so the resize handles are keyboard operable out of the box. Focus a handle and the arrow keys move it in small steps, which means keyboard users and assistive technology users can adjust the layout just like everyone else. The handle also carries the right ARIA role and value attributes so a screen reader announces it as a separator with a current position.
Don't undo that for free accessibility by hiding the handle visually. Give it a focus ring and enough hit area to grab, both with a pointer and a finger. A one-pixel divider is technically draggable but practically invisible, and on touch screens it's nearly impossible to land on. Widening the handle on hover, or adding a grip icon in the middle, makes the interaction obvious without cluttering the design.
Common Gotchas
- Container must have explicit dimensions -
PanelGroupneedswidthandheightto calculate sizes; useh-screenor set an explicit height. This is the number one reason a layout renders with zero-size panels. defaultSizevalues must sum to 100 - across allPanelcomponents in a group, or the library will normalize them in ways you didn't intend.- Resize handle styling - the handle renders a tiny element; make it visually clear (wider on hover) so users discover it.
- Unique
autoSaveIdper group - if two panel groups share an id, they'll overwrite each other's saved sizes in localStorage and produce confusing results.
Related
- TanStack Table - a common pattern is a resizable panel layout with a navigation tree on the left and a data table on the right
- cmdk - command palette that opens over any panel layout without disturbing the split view