CentercodeDitto
DittoPorygonAPI

ResizablePanel

User-resizable side panels with localStorage persistence and shared width synchronization

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
Navigation
Dashboard
Projects
Settings
Drag the right edge to resize
Main Content Area
Bounds: 240px - 480px (default: 300px)
Right Panel
Main Content Area
Property Panel
Name:Item 1
Status:
Active
Created:Jan 15
Drag the left edge to resize
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 panels
  • ResizeHandle - Standalone drag handle component (for custom implementations)
  • usePanelWidth - Hook for reading/setting shared panel width
  • useResizablePanelContext - Context hook for children to access resize state
  • PANEL_BOUNDS - Width constraints (min, max, default) per side
  • PANEL_STORAGE_KEYS - localStorage keys per side

Props Reference

ResizablePanel Props

PropTypeDefaultDescription
side'left' | 'right'requiredWhich side of the screen this panel is on
childrenReactNoderequiredContent to render inside the panel
classNamestring-Additional className for the container
resizablebooleantrueWhether resizing is enabled
onWidthChange(width: number) => void-Called when width changes during drag
onResizeEnd(width: number) => void-Called when resize ends (mouse up)
styleCSSProperties-Inline styles to merge with width
forcedWidthnumber-Force a specific width, overriding stored width (for expand/collapse)
storageKeystring-Custom localStorage key for independent width persistence
persistOnResizebooleantrueWhether to persist width changes to localStorage

usePanelWidth Return Value

PropTypeDefaultDescription
widthnumber-Current panel width in pixels
isHydratedboolean-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)

PropTypeDefaultDescription
left.minnumber240Minimum width for left panels
left.maxnumber480Maximum width for left panels
left.defaultnumber300Default width for left panels
right.minnumber480Minimum width for right panels
right.maxnumber1200Maximum width for right panels
right.defaultnumber768Default 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>
  );
}