Components
Resizable
Accessible, draggable panel groups for building split layouts — sidebars, editors, and dashboards — that the user can resize horizontally or vertically.
Basic usage
Wrap panels in a ResizablePanelGroup with a fixed height, and drag the divider to resize.
Vertical
Set direction="vertical" to stack panels and resize along the y-axis.
With handle
Pass withHandle to render a visible grip affordance that makes the divider easier to find and grab. The grip is a solid --control-edge chip — the same token as the divider — so it needs no extra outline to hold its own against the page.
Nested groups
Nest a vertical group inside a horizontal panel to compose full IDE-style layouts.
Remembering sizes
Give the group an autoSaveId and the layout survives a reload — the pattern behind the three-column list ▸ detail ▸ side-panel screens this component exists for. Combine it with collapsible so a panel can be tucked away to collapsedSize and come back the same width. Drag the divider below, reload the page, and it stays where you left it.
Props
ResizablePanelGroup
| Prop | Type | Default | Description |
|---|---|---|---|
directionrequired | 'horizontal' | 'vertical' | — | Axis the panels are laid out and resized along. |
autoSaveId | string | null | — | Persists panel sizes to localStorage under this id across reloads. |
storage | { getItem, setItem } | localStorage | Custom store backing autoSaveId — swap in a cookie or a server-synced store. |
keyboardResizeBy | number | null | null | Percentage each arrow-key press moves the divider; null falls back to the library’s built-in 10. Shift+Arrow always jumps the full 100 regardless. |
onLayout | (sizes: number[]) => void | — | Fires with the new percentage sizes whenever the layout changes. |
className | string | — | Additional Tailwind classes — set a fixed height here. |
...rest | PanelGroupProps | — | All react-resizable-panels PanelGroup props are forwarded; the element carries data-slot="resizable-panel-group". |
ResizablePanel
| Prop | Type | Default | Description |
|---|---|---|---|
defaultSize | number | — | Initial size as a percentage (0–100) of the group. Omitted panels split what is left evenly. |
minSize | number | 0 | Smallest size the panel can be dragged to, as a percentage. |
maxSize | number | 100 | Largest size the panel can be dragged to, as a percentage. |
collapsible | boolean | false | Allows the panel to snap past minSize down to collapsedSize. |
collapsedSize | number | 0 | Size the panel snaps to when collapsed. |
onCollapse | () => void | — | Fires when the panel reaches its collapsed size. |
onExpand | () => void | — | Fires when a collapsed panel opens again. |
onResize | (size: number, prevSize?: number) => void | — | Fires with this panel’s new percentage size on every change. |
order | number | — | Explicit order when panels are conditionally rendered. |
...rest | PanelProps | — | All react-resizable-panels Panel props are forwarded; the element carries data-slot="resizable-panel". |
ResizableHandle
| Prop | Type | Default | Description |
|---|---|---|---|
withHandle | boolean | false | Renders a visible grip in the centre of the divider. |
disabled | boolean | false | Prevents the handle from being dragged. |
hitAreaMargins | { coarse: number; fine: number } | { coarse: 15, fine: 5 } | Pixels of slack around the 1 px divider that still register as a grab — wider for touch than for a mouse. |
onDragging | (isDragging: boolean) => void | — | Fires on drag start and end — useful to suspend expensive renders while resizing. |
tabIndex | number | 0 | Tab order of the separator. The handle is keyboard-operable out of the box. |
className | string | — | Merged onto the divider, which is bg-control-edge and 1 px wide. |
...rest | PanelResizeHandleProps | — | All react-resizable-panels PanelResizeHandle props are forwarded; the element carries data-slot="resizable-handle". |
Accessibility
- Each handle is a focusable
role="separator"exposingaria-valuenow,aria-valuemin, andaria-valuemaxso assistive tech announces panel sizing. - Handles are keyboard-operable — focus with Tab and resize in
keyboardResizeBysteps of 10% with the arrow keys; Shift + arrow, and Home / End, jump to the min and max sizes. - A
focus-visiblering using the brand gold ring token appears on keyboard focus so the active divider is always visible. - The divider is filled with
--control-edge, not the decorative--borderstep. It is a real control, so WCAG 1.4.11 asks 3:1 of it, and going through the role token is what lets one declaration lift every divider. Underdata-contrast="high"control-edge measures 3.83:1 in light and 4.94:1 in dark. On the default palette it resolves to--input: the same #EDEEF1 step as border in light (1.16:1), but #434A5A in dark (2.00:1) where border sits at 1.10:1 — a known gap of that palette. - The visible grip rendered by
withHandleis decorative; the divider itself is 1 px wide buthitAreaMarginsextends the pointer target by 15 px on touch and 5 px with a mouse, so the target stays comfortable without a heavier line.