Overview
ResizablePanel provides drag-to-resize functionality for side panels. All panels on the same side share width (left panels share one width, right panels share another), with automatic localStorage persistence. The component is SSR-safe and handles hydration gracefully.
Key Features
- Drag-to-resize with configurable min/max bounds
- localStorage persistence for width across sessions
- Double-click reset to default width
- SSR-safe hydration (no flicker on initial load)
- Shared width per side (all left panels sync, all right panels sync)
- Custom storage keys for independent panel widths
- Context provider for children to access resize state
Examples
Left and Right Panels
Drag the edges to resize. Width persists to localStorage and syncs across panels.
Left Panel
Main Content Area
Bounds: 240px - 480px (default: 300px)
Right Panel
Main Content Area
Bounds: 480px - 1200px (default: 768px)
usePanelWidth Hook
Use the usePanelWidth hook to read and control panel width programmatically.
Left Panel State
width: 300px
isHydrated: false
Right Panel State
width: 768px
isHydrated: false
These values are read from localStorage and synced across all panels of the same side. Double-click the resize handle to reset.
Module Exports
ResizablePanel- Main wrapper component for resizable panelsResizeHandle- Standalone drag handle component (for custom implementations)usePanelWidth- Hook for reading/setting shared panel widthuseResizablePanelContext- Context hook for children to access resize statePANEL_BOUNDS- Width constraints (min, max, default) per sidePANEL_STORAGE_KEYS- localStorage keys per side
Props Reference
ResizablePanel Props
| Prop | Type | Default | Description |
|---|---|---|---|
side | 'left' | 'right' | required | Which side of the screen this panel is on |
children | ReactNode | required | Content to render inside the panel |
className | string | - | Additional className for the container |
resizable | boolean | true | Whether resizing is enabled |
onWidthChange | (width: number) => void | - | Called when width changes during drag |
onResizeEnd | (width: number) => void | - | Called when resize ends (mouse up) |
style | CSSProperties | - | Inline styles to merge with width |
forcedWidth | number | - | Force a specific width, overriding stored width (for expand/collapse) |
storageKey | string | - | Custom localStorage key for independent width persistence |
persistOnResize | boolean | true | Whether to persist width changes to localStorage |
usePanelWidth Return Value
| Prop | Type | Default | Description |
|---|---|---|---|
width | number | - | Current panel width in pixels |
isHydrated | boolean | - | Whether localStorage value has been loaded |
setWidth | (width: number) => void | - | Update width (also persists to localStorage) |
resetToDefault | () => void | - | Reset width to default value |
Panel Bounds (PANEL_BOUNDS)
| Prop | Type | Default | Description |
|---|---|---|---|
left.min | number | 240 | Minimum width for left panels |
left.max | number | 480 | Maximum width for left panels |
left.default | number | 300 | Default width for left panels |
right.min | number | 480 | Minimum width for right panels |
right.max | number | 1200 | Maximum width for right panels |
right.default | number | 768 | Default width for right panels |
Usage
import { ResizablePanel, usePanelWidth, PANEL_BOUNDS } from '@/ui/components/resizable-panel';
// Basic usage - Left sidebar
<ResizablePanel side="left" className="border-r">
<nav>Sidebar content</nav>
</ResizablePanel>
// Right panel with callbacks
<ResizablePanel
side="right"
className="border-l"
onWidthChange={(width) => console.log('Width:', width)}
onResizeEnd={(width) => console.log('Final:', width)}
>
<PropertyEditor />
</ResizablePanel>
// Custom storage key for independent width
<ResizablePanel
side="right"
storageKey="property-panel-settings"
>
<SettingsPanel />
</ResizablePanel>
// Using the hook for custom implementations
function CustomPanel() {
const { width, setWidth, resetToDefault, isHydrated } = usePanelWidth('left');
return (
<div style={{ width }}>
<button onClick={resetToDefault}>Reset Width</button>
</div>
);
}