rapidUI

Resizable

Resizable panel groups with drag handles, built on react-resizable-panels.

Installation

pnpm dlx shadcn@latest add https://rdf.dev.360online.com/r/resizable.json

Installs to @/components/ui/resizable.

Manual installation

Install the dependencies:

pnpm add react-resizable-panels@^4.14.1

Copy the source into your project:

components/ui/resizable.tsx
'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 useDefaultLayout hook
  • 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 percentage
  • onLayoutChange?: (layout: Layout) => void - Called continuously while resizing
  • onLayoutChanged?: (layout: Layout, meta) => void - Called once a resize has finished (use for persistence)
  • disabled?: boolean - Disable resizing for the whole group
  • groupRef? - Imperative API (getLayout, setLayout)
  • Extends standard div props

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 size
  • maxSize?: number | string - Maximum size
  • collapsible?: boolean - Whether the panel can be collapsed below minSize
  • collapsedSize?: number | string - Size when collapsed (defaults to 0%)
  • onResize?: (size: { asPercentage, inPixels }, id, prevSize) => void - Called when the panel resizes
  • panelRef? - 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 grip
  • disabled?: boolean - Whether the handle is disabled
  • disableDoubleClick?: 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 resizing
    • Arrow keys: Resize panels when focused
    • Home/End: Move to minimum/maximum size
    • Tab: 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

  1. 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
  2. User Experience

    • Use withHandle for 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
  3. Performance

    • Avoid complex content in panels during resize operations
    • Use onResize callbacks judiciously to prevent performance issues
    • Consider virtualizing content in large panels
    • Test with many panels to ensure smooth performance
  4. 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
  5. 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
  • Card
  • Tabs
  • Accordion
  • Sheet