React Resizable Panels in 2026: react-resizable-panels, Drag Handles, and Persistent Layout
Build resizable panel layouts in React with react-resizable-panels: drag handles, collapsible panels, keyboard navigation, persistent layout, and sidebar + code editor patterns.
React Resizable Panels in 2026: react-resizable-panels, Drag Handles, and Persistent Layout
Resizable panels are a staple of developer tools, data dashboards, and IDE-style interfaces. Users expect to drag a handle to resize the sidebar, collapse a panel to maximize working space, and have the layout persisted across sessions.
react-resizable-panels (by Brian Vaughn, the React DevTools author) is the 2026 standard for this โ it handles keyboard navigation, accessibility, SSR, and persistence out of the box. This post covers the common patterns: sidebar + main content, horizontal/vertical splits, collapsible panels, and saving layout to localStorage.
Installation
npm install react-resizable-panels
Basic Horizontal Split
// components/Layout/SplitLayout.tsx
"use client";
import {
PanelGroup,
Panel,
PanelResizeHandle,
} from "react-resizable-panels";
export function SplitLayout({
sidebar,
content,
}: {
sidebar: React.ReactNode;
content: React.ReactNode;
}) {
return (
<PanelGroup
direction="horizontal"
className="h-full"
// Persist layout to localStorage
autoSaveId="main-layout"
>
{/* Sidebar panel */}
<Panel
defaultSize={20} // 20% of available space
minSize={15} // Can't shrink below 15%
maxSize={40} // Can't grow beyond 40%
className="overflow-hidden"
>
{sidebar}
</Panel>
{/* Drag handle */}
<PanelResizeHandle className="w-1.5 bg-gray-100 hover:bg-blue-400 transition-colors cursor-col-resize" />
{/* Main content panel */}
<Panel className="overflow-hidden">
{content}
</Panel>
</PanelGroup>
);
}
๐ Looking for a Dev Team That Actually Delivers?
Most agencies sell you a project manager and assign juniors. Viprasol is different โ senior engineers only, direct Slack access, and a 5.0โ Upwork record across 100+ projects.
- React, Next.js, Node.js, TypeScript โ production-grade stack
- Fixed-price contracts โ no surprise invoices
- Full source code ownership from day one
- 90-day post-launch support included
Collapsible Sidebar
// components/Layout/CollapsibleSidebar.tsx
"use client";
import { useRef, useState } from "react";
import {
PanelGroup,
Panel,
PanelResizeHandle,
type ImperativePanelHandle,
} from "react-resizable-panels";
import { ChevronLeft, ChevronRight } from "lucide-react";
export function CollapsibleSidebar({
sidebar,
content,
}: {
sidebar: React.ReactNode;
content: React.ReactNode;
}) {
const sidebarRef = useRef<ImperativePanelHandle>(null);
const [collapsed, setCollapsed] = useState(false);
const toggleSidebar = () => {
const panel = sidebarRef.current;
if (!panel) return;
if (collapsed) {
panel.expand(); // Restore to last size (or defaultSize)
} else {
panel.collapse(); // Collapse to 0 (or minSize if not collapsible)
}
};
return (
<PanelGroup
direction="horizontal"
className="h-full"
autoSaveId="collapsible-sidebar-layout"
>
<Panel
ref={sidebarRef}
defaultSize={20}
minSize={0}
collapsible // Required for collapse() to work
collapsedSize={0} // Size when collapsed
onCollapse={() => setCollapsed(true)}
onExpand={() => setCollapsed(false)}
className="overflow-hidden"
>
<div
className={`h-full transition-opacity duration-150 ${
collapsed ? "opacity-0" : "opacity-100"
}`}
>
{sidebar}
</div>
</Panel>
<PanelResizeHandle className="relative w-1.5 bg-gray-100 hover:bg-blue-400 transition-colors cursor-col-resize group">
{/* Toggle button on the handle */}
<button
onClick={toggleSidebar}
className="absolute top-1/2 -translate-y-1/2 -right-3 z-10 flex h-6 w-6 items-center justify-center rounded-full bg-white border border-gray-200 shadow-sm hover:bg-gray-50 opacity-0 group-hover:opacity-100 transition-opacity"
aria-label={collapsed ? "Expand sidebar" : "Collapse sidebar"}
>
{collapsed ? (
<ChevronRight className="h-3 w-3 text-gray-500" />
) : (
<ChevronLeft className="h-3 w-3 text-gray-500" />
)}
</button>
</PanelResizeHandle>
<Panel className="overflow-hidden">
{content}
</Panel>
</PanelGroup>
);
}
IDE-Style Three-Panel Layout
// components/Layout/IdeLayout.tsx
"use client";
import {
PanelGroup,
Panel,
PanelResizeHandle,
type ImperativePanelHandle,
} from "react-resizable-panels";
import { useRef } from "react";
interface IdeLayoutProps {
fileTree: React.ReactNode;
editor: React.ReactNode;
terminal: React.ReactNode;
}
export function IdeLayout({ fileTree, editor, terminal }: IdeLayoutProps) {
const terminalRef = useRef<ImperativePanelHandle>(null);
return (
// Outer: horizontal split (file tree | editor + terminal)
<PanelGroup direction="horizontal" autoSaveId="ide-horizontal" className="h-screen">
{/* File tree */}
<Panel
defaultSize={18}
minSize={12}
maxSize={35}
collapsible
className="overflow-hidden border-r border-gray-800"
>
{fileTree}
</Panel>
<PanelResizeHandle className="w-px bg-gray-800 hover:bg-blue-500 transition-colors cursor-col-resize" />
{/* Right side: editor + terminal (vertical split) */}
<Panel>
<PanelGroup direction="vertical" autoSaveId="ide-vertical">
{/* Editor */}
<Panel defaultSize={70} minSize={30} className="overflow-hidden">
{editor}
</Panel>
<PanelResizeHandle className="h-px bg-gray-800 hover:bg-blue-500 transition-colors cursor-row-resize" />
{/* Terminal */}
<Panel
ref={terminalRef}
defaultSize={30}
minSize={10}
collapsible
className="overflow-hidden bg-gray-950"
>
{terminal}
</Panel>
</PanelGroup>
</Panel>
</PanelGroup>
);
}
๐ Senior Engineers. No Junior Handoffs. Ever.
You get the senior developer, not a project manager who relays your requirements to someone you never meet. Every Viprasol project has a senior lead from kickoff to launch.
- MVPs in 4โ8 weeks, full platforms in 3โ5 months
- Lighthouse 90+ performance scores standard
- Works across US, UK, AU timezones
- Free 30-min architecture review, no commitment
Custom Drag Handle with Visual Indicator
// components/Layout/ResizeHandle.tsx
"use client";
import { PanelResizeHandle } from "react-resizable-panels";
import { GripVertical, GripHorizontal } from "lucide-react";
import { useState } from "react";
interface ResizeHandleProps {
direction: "horizontal" | "vertical";
className?: string;
}
export function ResizeHandle({ direction, className }: ResizeHandleProps) {
const [isDragging, setIsDragging] = useState(false);
const isHorizontal = direction === "horizontal";
return (
<PanelResizeHandle
onDragging={setIsDragging}
className={`
relative flex items-center justify-center
${isHorizontal ? "w-2 cursor-col-resize" : "h-2 cursor-row-resize"}
${isDragging
? "bg-blue-500"
: "bg-gray-100 hover:bg-gray-200"
}
transition-colors group
${className ?? ""}
`}
>
{/* Drag grip icon */}
<div
className={`
absolute flex items-center justify-center
h-6 w-6 rounded-sm bg-white border border-gray-200 shadow-sm
opacity-0 group-hover:opacity-100 transition-opacity
${isDragging ? "opacity-100" : ""}
`}
>
{isHorizontal ? (
<GripVertical className="h-4 w-4 text-gray-400" />
) : (
<GripHorizontal className="h-4 w-4 text-gray-400" />
)}
</div>
</PanelResizeHandle>
);
}
Programmatic Control and Keyboard Shortcuts
// components/Layout/LayoutWithKeyboard.tsx
"use client";
import { useEffect, useRef } from "react";
import {
PanelGroup,
Panel,
PanelResizeHandle,
type ImperativePanelHandle,
} from "react-resizable-panels";
export function LayoutWithKeyboard({
sidebar,
content,
}: {
sidebar: React.ReactNode;
content: React.ReactNode;
}) {
const sidebarRef = useRef<ImperativePanelHandle>(null);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
// Ctrl+B: toggle sidebar (VS Code-style)
if (e.ctrlKey && e.key === "b") {
e.preventDefault();
const panel = sidebarRef.current;
if (!panel) return;
if (panel.isCollapsed()) {
panel.expand();
} else {
panel.collapse();
}
}
// Ctrl+Shift+`: toggle terminal (if you have one)
if (e.ctrlKey && e.shiftKey && e.key === "`") {
e.preventDefault();
// Toggle terminal panel similarly
}
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, []);
return (
<PanelGroup direction="horizontal" autoSaveId="keyboard-layout" className="h-full">
<Panel
ref={sidebarRef}
defaultSize={22}
minSize={15}
collapsible
>
{sidebar}
</Panel>
<PanelResizeHandle className="w-1 bg-gray-200 hover:bg-blue-400 cursor-col-resize" />
<Panel>{content}</Panel>
</PanelGroup>
);
}
Custom Layout Persistence (Beyond autoSaveId)
autoSaveId saves to localStorage automatically, but you might want to persist per-user on the server:
// hooks/usePersistentLayout.ts
"use client";
import { useCallback } from "react";
import { useMutation, useQuery } from "@tanstack/react-query";
export function usePersistentLayout(layoutKey: string) {
const { data: savedLayout } = useQuery({
queryKey: ["layout", layoutKey],
queryFn: async () => {
const res = await fetch(`/api/user/layout?key=${layoutKey}`);
if (!res.ok) return null;
return res.json() as Promise<number[]>; // Panel sizes as percentages
},
});
const { mutate: saveLayout } = useMutation({
mutationFn: (sizes: number[]) =>
fetch("/api/user/layout", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ key: layoutKey, sizes }),
}),
});
const onLayout = useCallback(
(sizes: number[]) => {
// Debounce saves โ don't fire on every drag pixel
saveLayout(sizes);
},
[saveLayout]
);
return { savedLayout, onLayout };
}
// Usage:
export function PersistentLayout({ sidebar, content }: Props) {
const { savedLayout, onLayout } = usePersistentLayout("main-sidebar");
return (
<PanelGroup
direction="horizontal"
onLayout={onLayout}
// No autoSaveId โ we handle persistence ourselves
>
<Panel defaultSize={savedLayout?.[0] ?? 20} minSize={15}>
{sidebar}
</Panel>
<PanelResizeHandle className="w-1 bg-gray-200 hover:bg-blue-400 cursor-col-resize" />
<Panel defaultSize={savedLayout?.[1] ?? 80}>
{content}
</Panel>
</PanelGroup>
);
}
Accessibility
react-resizable-panels includes built-in keyboard navigation for the resize handles:
- Arrow keys: Resize panels when handle is focused
- Home/End: Snap to min/max size
- Enter: Reset to default size
// Add aria-label to the handle for screen readers
<PanelResizeHandle
aria-label="Resize sidebar"
className="w-1.5 bg-gray-200 hover:bg-blue-400 cursor-col-resize focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
Cost and Timeline
| Component | Timeline | Cost (USD) |
|---|---|---|
| Basic split layout | 0.5 day | $300โ$500 |
| Collapsible sidebar with toggle | 0.5โ1 day | $400โ$800 |
| IDE-style nested panels | 1โ2 days | $800โ$1,600 |
| Custom drag handle + keyboard shortcuts | 0.5 day | $300โ$500 |
| Server-persisted layout | 0.5โ1 day | $400โ$800 |
| Full IDE-style layout system | 1โ2 weeks | $5,000โ$8,000 |
See Also
- React DnD Kit โ Drag-and-drop within panels
- React Virtualized Lists โ Virtualizing long lists inside panels
- Next.js App Router Caching โ Persisting layout state server-side
- React Compound Components โ Composable layout primitives
Working With Viprasol
We build IDE-style and dashboard layouts with resizable panels for developer tools, data platforms, and internal applications. Our team has shipped multi-panel layouts with nested splits, programmatic collapse, keyboard shortcuts, and server-persisted layout preferences.
What we deliver:
- PanelGroup setup with autoSaveId or server-side persistence
- Collapsible sidebar with keyboard toggle (Ctrl+B)
- IDE-style horizontal + vertical nested splits
- Custom drag handles with grip icons and drag state
- Responsive behavior (stack vertically on mobile)
Explore our web development services or contact us to build your resizable panel interface.
About the Author
Viprasol Tech Team
Custom Software Development Specialists
The Viprasol Tech team specialises in algorithmic trading software, AI agent systems, and SaaS development. With 100+ projects delivered across MT4/MT5 EAs, fintech platforms, and production AI systems, the team brings deep technical experience to every engagement. Based in India, serving clients globally.
Need a Modern Web Application?
From landing pages to complex SaaS platforms โ we build it all with Next.js and React.
Free consultation โข No commitment โข Response within 24 hours
Need a custom web application built?
We build React and Next.js web applications with Lighthouse โฅ90 scores, mobile-first design, and full source code ownership. Senior engineers only โ from architecture through deployment.