Resizable
Panels you can drag apart, with a quiet divider that wakes up on hover, sizes that glide on reset or collapse, and layouts that persist.
pnpm dlx shadcn@latest add https://hextaui.com/r/resizable.jsonAdds the component, the HextaUI theme tokens and any HextaUI components it depends on.
Add the theme tokens to your global CSS, if you haven’t yet.
Install the dependencies.
pnpm add react-resizable-panels cnCopy and paste the following code into your project.
components/ui/resizable.tsx Update the import paths to match your project setup.
Numbers are pixels and strings are percentages, so defaultSize="30%" and minSize={240} both work. The divider stays a hairline until you reach for it. While you drag, panels follow the pointer exactly; every other change glides, including double-click resets, keyboard steps and collapsing from code.
Collapsible
A collapsible panel snaps shut when dragged below its minSize. Toggle it from a button with usePanelRef; the collapse animates.
Size readout
showSize shows the panel before the divider while you drag or use the keyboard, in "percent" or "pixels".
Remember the layout
useDefaultLayout saves sizes to localStorage and restores them on the next visit without animating in. Give each panel an id.
Vertical
orientation="vertical" stacks panels. Groups nest, as in the demo above.
Visible grip
withHandle keeps the grip showing instead of revealing it on hover.
Right to left
Panels and arrow keys follow the reading direction.
| Key | Action |
|---|---|
| ←→↑↓ | Moves the focused divider by 5% along its group's direction. |
| HomeEnd | Moves the divider as far as the panels allow. |
| Enter | Collapses or expands a collapsible panel. |
| F6 | Moves to the next divider in the group; Shift goes back. |
- Each divider is a focusable
separatorthat reports the size of the panel before it. - The size readout is decorative; screen readers get the separator's own value.
- With reduced motion, sizes change instantly and the divider doesn't animate.
| Prop | Type | Default |
|---|---|---|
orientation | "horizontal" | "vertical" | "horizontal" |
defaultLayout | Layout | – |
onLayoutChangeEvery change, including while dragging. | (layout) => void | – |
onLayoutChangedOnce a change settles. | (layout, meta) => void | – |
disabled | boolean | false |
groupRef | Ref<GroupImperativeHandle> | – |
| Attribute | Description |
|---|---|
data-slot="resizable-panel-group" | The group. |
data-ready | Set after mount; sizes animate from then on. |
| Prop | Type | Default |
|---|---|---|
defaultSize | number | string | – |
minSize | number | string | – |
maxSize | number | string | – |
collapsible | boolean | false |
collapsedSize | number | string | 0 |
onResize | (size, id, previous) => void | – |
panelRef | Ref<PanelImperativeHandle> | – |
idNeeded to remember layouts. | string | – |
| Prop | Type | Default |
|---|---|---|
withHandleAlways show the grip. | boolean | false |
showSize | boolean | "percent" | "pixels" | false |
disableDoubleClickStop double-click from resetting the panels. | boolean | false |
disabled | boolean | false |
| Attribute | Description |
|---|---|
data-slot="resizable-handle" | The divider. |
data-separator | "inactive", "hover", "active", "focus" or "disabled". |
data-slot="resizable-handle-grip" | The grip pill. |
usePanelRef and useGroupRef give you collapse, expand, resize and setLayout. useDefaultLayout persists a group's layout.