Installation
pnpm dlx shadcn@latest add https://rdf.dev.360online.com/r/resizable.jsonInstalls to @/components/ui/resizable.
Manual installation
Install the dependencies:
pnpm add react-resizable-panels@^4.14.1Copy the source into your project:
'use client'
import * as ResizablePrimitive from 'react-resizable-panels'
import { cn } from '@/lib/utils'
function ResizablePanelGroup({ className, ...props }: ResizablePrimitive.GroupProps) {
return (
<ResizablePrimitive.Group
data-slot="resizable-panel-group"
className={cn('flex h-full w-full aria-[orientation=vertical]:flex-col', className)}
{...props}
/>
)
}
function ResizablePanel({ ...props }: ResizablePrimitive.PanelProps) {
return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} />
}
function ResizableHandle({
withHandle,
className,
...props
}: ResizablePrimitive.SeparatorProps & {
withHandle?: boolean
}) {
return (
<ResizablePrimitive.Separator
data-slot="resizable-handle"
className={cn(
'relative flex w-px items-center justify-center bg-border ring-offset-background after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-hidden aria-[orientation=horizontal]:h-px aria-[orientation=horizontal]:w-full aria-[orientation=horizontal]:after:left-0 aria-[orientation=horizontal]:after:h-1 aria-[orientation=horizontal]:after:w-full aria-[orientation=horizontal]:after:translate-x-0 aria-[orientation=horizontal]:after:-translate-y-1/2 [&[aria-orientation=horizontal]>div]:rotate-90',
className,
)}
{...props}
>
{withHandle && <div className="z-10 flex h-6 w-1 shrink-0 rounded-lg bg-border" />}
</ResizablePrimitive.Separator>
)
}
export { ResizableHandle, ResizablePanel, ResizablePanelGroup }A resizable panel component that allows users to adjust the size of panels by dragging handles. Built on react-resizable-panels v4 with support for horizontal and vertical layouts.
Sizes: in v4, numeric size props are pixels. Use strings for percentages, e.g.
defaultSize="50%"(a string without a unit, like"50", is also treated as a percentage). Other CSS units (px,rem,em,vh,vw) are supported too.
Features
- Horizontal and vertical panel layouts
- Draggable resize handles with optional grip indicators
- Minimum and maximum size constraints
- Nested panel groups for complex layouts
- Sizes in percentages, pixels or other CSS units
- Collapsible panels with an imperative API (
panelRef) - Persisted layouts via the
useDefaultLayouthook - Keyboard accessibility and screen reader support
- Touch support
- Built on top of react-resizable-panels v4
Usage
import {
ResizablePanelGroup,
ResizablePanel,
ResizableHandle,
} from "@/components/ui/resizable"
// Basic horizontal layout
<ResizablePanelGroup orientation="horizontal" className="max-w-md rounded-lg border">
<ResizablePanel defaultSize="50%">
<div className="flex h-[200px] items-center justify-center p-6">
<span className="font-semibold">One</span>
</div>
</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize="50%">
<div className="flex h-[200px] items-center justify-center p-6">
<span className="font-semibold">Two</span>
</div>
</ResizablePanel>
</ResizablePanelGroup>
// With visible handle grip
<ResizablePanelGroup orientation="horizontal">
<ResizablePanel defaultSize="50%">Content</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize="50%">Content</ResizablePanel>
</ResizablePanelGroup>
// Vertical layout
<ResizablePanelGroup orientation="vertical">
<ResizablePanel defaultSize="50%">Top Content</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize="50%">Bottom Content</ResizablePanel>
</ResizablePanelGroup>
// With size constraints
<ResizablePanelGroup orientation="horizontal">
<ResizablePanel defaultSize="30%" minSize="20%" maxSize="60%">
Constrained Panel
</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize="70%">
Flexible Panel
</ResizablePanel>
</ResizablePanelGroup>
// Nested panels
<ResizablePanelGroup orientation="horizontal">
<ResizablePanel defaultSize="50%">Left Panel</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize="50%">
<ResizablePanelGroup orientation="vertical">
<ResizablePanel defaultSize="50%">Top Right</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize="50%">Bottom Right</ResizablePanel>
</ResizablePanelGroup>
</ResizablePanel>
</ResizablePanelGroup>Examples
Default
A case list next to the selected case. The list has a minimum size of 25%.
With Handle
A document list and preview with a visible grip on the handle.
Vertical
A council agenda above the minutes for the selected item.
Three Panels
Folders, a case list and case details, each with its own handle.
With Min/Max Constraints
Property details limited to 25–60% of the width, next to a map placeholder.
Nested Panels
A vertical group nested inside a horizontal one.
Case Workspace
A full case handling workspace: case list, details and applicant messages in nested groups.
Collapsible
The case list collapses and expands through the imperative panelRef API.
Persisted Layout
Panel sizes saved to localStorage with useDefaultLayout. Resize, then reload.
Props
ResizablePanelGroup
Wraps react-resizable-panels Group.
orientation?: "horizontal" | "vertical"- Layout orientation (defaults to"horizontal")defaultLayout?: Layout- Initial layout as a map of panel id to percentageonLayoutChange?: (layout: Layout) => void- Called continuously while resizingonLayoutChanged?: (layout: Layout, meta) => void- Called once a resize has finished (use for persistence)disabled?: boolean- Disable resizing for the whole groupgroupRef?- Imperative API (getLayout,setLayout)- Extends standard
divprops
ResizablePanel
Wraps react-resizable-panels Panel.
id?: string- Panel id (required for persisted layouts)defaultSize?: number | string- Default size ("30%","240px", … — numbers are pixels)minSize?: number | string- Minimum sizemaxSize?: number | string- Maximum sizecollapsible?: boolean- Whether the panel can be collapsed belowminSizecollapsedSize?: number | string- Size when collapsed (defaults to 0%)onResize?: (size: { asPercentage, inPixels }, id, prevSize) => void- Called when the panel resizespanelRef?- Imperative API (collapse,expand,isCollapsed,getSize,resize)disabled?: boolean- Prevent this panel from being resized
ResizableHandle
Wraps react-resizable-panels Separator.
withHandle?: boolean- Whether to show a visible gripdisabled?: boolean- Whether the handle is disableddisableDoubleClick?: boolean- Disable resetting to the default size on double-click
Advanced Features
Persisted Layouts
import { useDefaultLayout } from 'react-resizable-panels'
const { defaultLayout, onLayoutChanged } = useDefaultLayout({ id: 'my-layout', storage: localStorage })
<ResizablePanelGroup defaultLayout={defaultLayout} onLayoutChanged={onLayoutChanged}>
<ResizablePanel id="nav" defaultSize="30%">Panel 1</ResizablePanel>
<ResizableHandle />
<ResizablePanel id="main" defaultSize="70%">Panel 2</ResizablePanel>
</ResizablePanelGroup>Panel Collapse
import { usePanelRef } from 'react-resizable-panels'
const panelRef = usePanelRef()
<ResizablePanel panelRef={panelRef} defaultSize="30%" minSize="15%" collapsible collapsedSize="5%">
Collapsible Panel
</ResizablePanel>
<Button onClick={() => panelRef.current?.collapse()}>Collapse</Button>Accessibility
The Resizable component follows WCAG 2.1 guidelines and provides:
- Proper ARIA roles and attributes for resize handles
- Keyboard navigation support:
Enter/Space: Start/stop resizingArrow keys: Resize panels when focusedHome/End: Move to minimum/maximum sizeTab: Navigate between handles
- Focus management with visible focus indicators
- Screen reader announcements for size changes
- Touch and mouse interaction support
- High contrast mode compatibility
Best Practices
-
Layout Design
- Use appropriate default sizes that work well for your content
- Set reasonable minimum and maximum constraints
- Consider the content hierarchy when nesting panels
- Test layouts on different screen sizes
-
User Experience
- Use
withHandlefor better discoverability of resize functionality - Provide visual feedback during resize operations
- Consider persisting layouts (
useDefaultLayout) for returning users - Don't make panels too narrow for their content
- Use
-
Performance
- Avoid complex content in panels during resize operations
- Use
onResizecallbacks judiciously to prevent performance issues - Consider virtualizing content in large panels
- Test with many panels to ensure smooth performance
-
Responsive Design
- Consider different interaction methods (mouse, touch, keyboard)
- Test on mobile devices where drag interactions may differ
- Provide adequate touch targets for resize handles
- Consider collapsible panels for mobile layouts
-
Content Management
- Ensure content adapts gracefully to size changes
- Use appropriate overflow handling (scroll, wrap, truncate)
- Consider minimum content requirements when setting minSize
- Provide loading states for dynamic content
Common Use Cases
- Code Editors: File explorer, editor, and output panels
- Dashboards: Customizable widget layouts
- Email Clients: Folder tree, message list, and preview pane
- Design Tools: Tools palette, canvas, and properties panel
- Data Analysis: Charts, filters, and data tables
- Documentation: Navigation, content, and table of contents
Related Components
- Card
- Tabs
- Accordion
- Sheet