Skip to Content
Puzzle

Resizable Panels

Split an area into panels that users resize with a pointer or keyboard.

Import

import { ResizablePanels } from "@puzzle-ui/ui";

ResizablePanel and ResizableHandle are also exported individually and are the same components as ResizablePanels.Panel and ResizablePanels.Handle.

Usage

Alternate ResizablePanels.Panel and ResizablePanels.Handle children, starting and ending with a panel. A group needs at least two panels. Each handle resizes the two panels next to it while keeping their combined size.

The group fills its parent, so give the parent a height. Panels scroll their own overflowing content.

390 × 280

Sizes

Sizes are percentages of the space left after handles and padding. Panel sizes are normalized to a total of 100%: defaultSizes={[1, 2]} produces 33.3% and 66.7%. Without sizes, the space is divided equally.

Size constraints

Use minSize and maxSize on a panel to limit it, in percentages. Initial sizes outside the limits are clamped, and the remaining space is redistributed proportionally across the other panels. The constraints of all panels must be able to add up to exactly 100%; otherwise a RangeError is thrown.

390 × 280

Vertical orientation

Set orientation="vertical" to stack panels from top to bottom. Handles then render as horizontal dividers.

390 × 340

Controlled sizes

Use sizes with onSizesChange to own the layout, for example to persist it. The callback receives normalized percentages. For uncontrolled usage, pass defaultSizes and optionally listen with onSizesChange.

390 × 320

Disabled

Set disabled on the group to lock every handle, or on a single handle to lock only that one. Disabled handles are removed from the tab order.

390 × 240

Accessibility

Each handle is a focusable separator that controls its two adjacent panels through aria-controls. It exposes the size of the panel before it with aria-valuenow, aria-valuemin, aria-valuemax, and a percentage aria-valuetext. The default accessible name is "Resize panels"; pass a descriptive aria-label when a group has several handles.

KeyAction
ArrowLeft / ArrowRightResize horizontal panels by keyboardStep. Mirrored in right-to-left layouts.
ArrowUp / ArrowDownResize vertical panels by keyboardStep.
Shift + arrow keyResize by ten times keyboardStep.
Home / EndShrink or grow the panel before the handle to its limit.

Pointer resizing supports mouse, touch, and pen input, and prevents text selection while dragging.

API

ResizablePanels

PropTypeDefaultDescription
childrenReactNodeRequiredAlternating panels and handles.
orientation"horizontal" | "vertical""horizontal"Layout direction.
defaultSizesnumber[]Equal sizesInitial sizes for uncontrolled usage.
sizesnumber[]-Controlled sizes in percentages.
onSizesChange(sizes: number[]) => void-Called with normalized sizes after every change.
disabledbooleanfalseDisables all resizing.
keyboardStepnumber1Percentage change per arrow key press.

The number of sizes must match the number of panels. Native div props and refs are forwarded; the root defaults to role="group".

ResizablePanels.Panel

PropTypeDefaultDescription
minSizenumber0Minimum size in percentages.
maxSizenumber100Maximum size in percentages.

Native div props and refs are forwarded. Panel IDs must be unique within a group; IDs are generated when omitted.

ResizablePanels.Handle

PropTypeDefaultDescription
withGripbooleanfalseRenders a visible grip instead of children.
disabledbooleanfalseDisables resizing through this handle.
aria-labelstring"Resize panels"Accessible name of the handle.
childrenReactNode-Custom handle content when there is no grip.

Native div props are forwarded, and event handlers such as onPointerDown and onKeyDown run before the built-in resizing. Call event.preventDefault() to skip it.

Styling

The handle's hit area uses --puzzle-resizable-panels-handle-size, which defaults to var(--puzzle-space-3). Override it on the group to change the handle size:

.editor-layout {
  --puzzle-resizable-panels-handle-size: var(--puzzle-space-4);
}

The divider line uses the theme's border colors and shows the focus ring color while dragging. The state is reflected in data-orientation, data-disabled, and data-resizing attributes.

;