Usage
import { Resizable } from "@voila.dev/ui/resizable";<div className="h-48">
<Resizable.PanelGroup>
<Resizable.Panel defaultSize={50}>…</Resizable.Panel>
<Resizable.Handle withHandle />
<Resizable.Panel defaultSize={50}>…</Resizable.Panel>
</Resizable.PanelGroup>
</div>For the split views of an application shell — a list beside a detail pane, an editor above its output — where the split is the user's to set and worth keeping. It is not a responsive layout primitive: on a phone the panels are still side by side and each is half as wide, so swap to a stacked layout at your breakpoint rather than shipping a 160px pane. And if the second panel only appears in response to an action, a Sheet or a Drawer is the lighter answer, since neither costs the first panel any width.
The group fills its parent, so the size goes on a wrapper and never on
Resizable.PanelGroup itself — a group in a parent with no height collapses to
nothing.
Recipes
Vertical, with a panel that snaps shut
orientation="vertical" stacks the panels and turns the handle horizontal.
Note the inversion the library inherits: a horizontal group is one whose panels
sit side by side, and its separators are vertical rules.
A panel with collapsible and collapsedSize={0} snaps shut when it is dragged
past its minimum instead of stopping there, and snaps back open on the next
drag. Pair it with minSize so the panel has a floor to be dragged past.
withHandle draws a grip in the middle of the handle. The drag target is the
same either way — it is a hit area wider than the hairline — so the grip is
about making the affordance visible on a surface where a 1px rule does not read
as draggable.
API
Resizable.Root and Resizable.PanelGroup are the same component; the alias
keeps the part name the underlying library uses, so the two tables below are
identical by design.
Resizable.Root
| Prop | Type | Default | |
|---|---|---|---|
children | ReactNode | — | Panel and Separator components that comprise this group. |
className | string | — | CSS class name. |
defaultLayout | Layout | — | Default layout for the Group. ℹ️ This value allows layouts to be remembered between page reloads. ⚠️ Slight layout shift may occur when server-rendering panels with percentage-based default sizes. Refer to the documentation for suggestions on how to minimize the impact of this. |
disableCursor | boolean | — | This library sets custom mouse cursor styles to indicate drag state. Use this prop to disable that behavior for Panels and Separators in this group. |
disabled | boolean | — | Disable resize functionality. |
elementRef | Ref<HTMLDivElement | null> | — | Ref attached to the root HTMLDivElement. |
groupRef | Ref<GroupImperativeHandle | null> | — | Exposes the following imperative API: ℹ️ The useGroupRef and useGroupCallbackRef hooks are exported for convenience use in TypeScript projects. |
id | string | — | Uniquely identifies this group within an application.
Falls back to useId when not provided. ℹ️ This value will also be assigned to the data-group attribute. |
onLayoutChange | ((layout: Layout) => void | undefined) | — | Called when the Group's layout is changing. ⚠️ For layout changes caused by pointer events, this method is called each time the pointer is moved.
For most cases, it is recommended to use the onLayoutChanged callback instead. |
onLayoutChanged | ((layout: Layout, meta: LayoutChangedMeta) => void) | — | Called after the Group's layout has been changed. ℹ️ For layout changes caused by pointer events, this method is not called until the pointer has been released.
This method is recommended when saving layouts to some storage api. ℹ️ The second argument contains meta information about the layout change.
The isUserInteraction attribute signals whether the resize was caused by direct user input.
It is true for resizes caused by pointer or keyboard input
and false for other triggers (e.g. imperative API calls, initial mount, etc.) |
orientation | "horizontal" | "vertical" | — | Specifies the resizable orientation ("horizontal" or "vertical"); defaults to "horizontal" |
resizeTargetMinimumSize | { coarse: number; fine: number; } | — | Minimum size of the resizable hit target area (either Separator or Panel edge)
This threshold ensures are large enough to avoid mis-clicks. ℹ️ Apple interface guidelines suggest 20pt (27px) on desktops and 28pt (37px) for touch devices
In practice this seems to be much larger than many of their own applications use though. |
style | CSSProperties | — | CSS properties. ⚠️ The following styles cannot be overridden: display, flex-direction, flex-wrap, and overflow. |
Plus the DOM props of the element it renders. Source: resizable/components/resizable-panel-group.tsx.
Resizable.Handle
| Prop | Type | Default | |
|---|---|---|---|
withHandle | boolean | — | Draw a grip in the middle of the handle. The drag target is the same either way — the grip only makes it visible on a surface where the hairline alone would not read as draggable. |
className | string | — | CSS class name. ℹ️ Use the data-separator attribute for custom hover and active styles ⚠️ The following properties cannot be overridden: flex-grow, flex-shrink |
disabled | boolean | — | When disabled, the separator cannot be used to resize its neighboring panels. ℹ️ The panels may still be resized indirectly (while other panels are being resized). To prevent a panel from being resized at all, it needs to also be disabled. |
disableDoubleClick | boolean | — | When true, double-clicking this Separator will not reset its Panel to its default size. |
elementRef | Ref<HTMLDivElement> | — | Ref attached to the root HTMLDivElement. |
id | string | — | Uniquely identifies the separator within the parent group.
Falls back to useId when not provided. ℹ️ This value will also be assigned to the data-separator attribute. |
style | CSSProperties | — | CSS properties. ℹ️ Use the data-separator attribute for custom hover and active styles ⚠️ The following properties cannot be overridden: flex-grow, flex-shrink |
Plus the DOM props of the element it renders. Source: resizable/components/resizable-handle.tsx.
Resizable.Panel
| Prop | Type | Default | |
|---|---|---|---|
className | string | — | CSS class name. ⚠️ Class is applied to nested HTMLDivElement to avoid styles that interfere with Flex layout. |
collapsedSize | string | number | — | Panel size when collapsed; defaults to 0%. |
collapsible | boolean | — | This panel can be collapsed. ℹ️ A collapsible panel will collapse when it's size is less than of the specified minSize |
defaultSize | string | number | — | Default size of Panel within its parent group; default is auto-assigned based on the total number of Panels. ℹ️ Interpretation rules: ⚠️ Percentage based sizes may cause slight layout shift when server-rendering. For more information see the documentation. |
disabled | boolean | — | When disabled, a panel cannot be resized either directly or indirectly (by resizing another panel). |
elementRef | Ref<HTMLDivElement | null> | — | Ref attached to the root HTMLDivElement. |
groupResizeBehavior | "preserve-pixel-size" | "preserve-relative-size" | — | How should this Panel behave if the parent Group is resized?
Defaults to preserve-relative-size. ℹ️ Panel min/max size constraints may impact this behavior. ⚠️ A Group must contain at least one Panel with preserve-relative-size resize behavior. |
id | string | — | Uniquely identifies this panel within the parent group.
Falls back to useId when not provided. ℹ️ This prop is used to associate persisted group layouts with the original panel. ℹ️ This value will also be assigned to the data-panel attribute. |
maxSize | string | number | — | Maximum size of Panel within its parent group; defaults to "100%". ℹ️ Interpretation rules: |
minSize | string | number | — | Minimum size of Panel within its parent group; defaults to 0%. ℹ️ Interpretation rules: |
onResize | ((panelSize: PanelSize, id: string | number | undefined, prevPanelSize: PanelSize | undefined) => void) | — | Called when panel sizes change. |
panelRef | Ref<PanelImperativeHandle | null> | — | Exposes the following imperative API: ℹ️ The usePanelRef and usePanelCallbackRef hooks are exported for convenience use in TypeScript projects. |
style | CSSProperties | — | CSS properties. ⚠️ Style is applied to nested HTMLDivElement to avoid styles that interfere with Flex layout. |
Plus the DOM props of the element it renders. Source: resizable/components/resizable-panel.tsx.
Resizable.PanelGroup
| Prop | Type | Default | |
|---|---|---|---|
children | ReactNode | — | Panel and Separator components that comprise this group. |
className | string | — | CSS class name. |
defaultLayout | Layout | — | Default layout for the Group. ℹ️ This value allows layouts to be remembered between page reloads. ⚠️ Slight layout shift may occur when server-rendering panels with percentage-based default sizes. Refer to the documentation for suggestions on how to minimize the impact of this. |
disableCursor | boolean | — | This library sets custom mouse cursor styles to indicate drag state. Use this prop to disable that behavior for Panels and Separators in this group. |
disabled | boolean | — | Disable resize functionality. |
elementRef | Ref<HTMLDivElement | null> | — | Ref attached to the root HTMLDivElement. |
groupRef | Ref<GroupImperativeHandle | null> | — | Exposes the following imperative API: ℹ️ The useGroupRef and useGroupCallbackRef hooks are exported for convenience use in TypeScript projects. |
id | string | — | Uniquely identifies this group within an application.
Falls back to useId when not provided. ℹ️ This value will also be assigned to the data-group attribute. |
onLayoutChange | ((layout: Layout) => void | undefined) | — | Called when the Group's layout is changing. ⚠️ For layout changes caused by pointer events, this method is called each time the pointer is moved.
For most cases, it is recommended to use the onLayoutChanged callback instead. |
onLayoutChanged | ((layout: Layout, meta: LayoutChangedMeta) => void) | — | Called after the Group's layout has been changed. ℹ️ For layout changes caused by pointer events, this method is not called until the pointer has been released.
This method is recommended when saving layouts to some storage api. ℹ️ The second argument contains meta information about the layout change.
The isUserInteraction attribute signals whether the resize was caused by direct user input.
It is true for resizes caused by pointer or keyboard input
and false for other triggers (e.g. imperative API calls, initial mount, etc.) |
orientation | "horizontal" | "vertical" | — | Specifies the resizable orientation ("horizontal" or "vertical"); defaults to "horizontal" |
resizeTargetMinimumSize | { coarse: number; fine: number; } | — | Minimum size of the resizable hit target area (either Separator or Panel edge)
This threshold ensures are large enough to avoid mis-clicks. ℹ️ Apple interface guidelines suggest 20pt (27px) on desktops and 28pt (37px) for touch devices
In practice this seems to be much larger than many of their own applications use though. |
style | CSSProperties | — | CSS properties. ⚠️ The following styles cannot be overridden: display, flex-direction, flex-wrap, and overflow. |
Plus the DOM props of the element it renders. Source: resizable/components/resizable-panel-group.tsx.
The panel and group props above come from
react-resizable-panels,
which owns the layout maths, the imperative handles (groupRef, panelRef)
and the persistence hooks; only withHandle is this library's.
Related
ScrollArea · Sidebar · Sheet