Resizable
Accessible resizable panel groups with keyboard support. Built on the @areia/slots resizable primitive.
Resizable wires panel sizing, drag handles, keyboard navigation, and layout change events.
Panel sizes and ilha morph
defaultSize is a percentage of the group (values should sum to about 100). When only some panels set defaultSize, Areia fills missing siblings with the remaining space in markup so SSR flex-grow stays coherent (e.g. 20 + omitted → 20 / 80, not 20 / 1).
After mount, @areia/slots owns each panel’s inline style (flex-grow percentages) and the root’s flex container styles. Under ilha, parent island re-renders morph the tree from template markup. Resizable stamps data-morph-preserve="style" on the root, panels, and handles by default (merged with any user data-morph-preserve tokens) so dragged sizes survive filters, loaders, and nested layout updates. You do not need to add data-morph-preserve="style" yourself.
Nested groups (shell sidebar + detail split) work the same way. Persist layout across full remounts with onLayoutChange if you want — that is optional app state, not required for morph safety.
import ilha from "ilha";
import { Resizable } from "areia";
export default ilha.render(() => (
<Resizable
direction="horizontal"
class="h-64 max-w-md rounded-lg border border-areia-border"
>
<Resizable.Panel defaultSize={50} minSize={10} collapsible>
<div class="flex items-center justify-center p-4">
<span class="font-semibold">Left</span>
</div>
</Resizable.Panel>
<Resizable.Handle withHandle />
<Resizable.Panel defaultSize={50} minSize={10}>
<div class="flex items-center justify-center p-4">
<span class="font-semibold">Right</span>
</div>
</Resizable.Panel>
</Resizable>
));Import
import { Resizable } from "areia";
Usage
Compose panels and handles within a root group. The root must be a direct parent of alternating panels and handles.
import ilha from "ilha";
import { Resizable } from "areia";
export default ilha.render(() => (
<Resizable
direction="horizontal"
class="h-64 max-w-md rounded-lg border border-areia-border"
>
<Resizable.Panel defaultSize={50}>
<div class="flex items-center justify-center p-4">
<span class="font-semibold">Left</span>
</div>
</Resizable.Panel>
<Resizable.Handle />
<Resizable.Panel defaultSize={50}>
<div class="flex items-center justify-center p-4">
<span class="font-semibold">Right</span>
</div>
</Resizable.Panel>
</Resizable>
));Composition
Use the following composition to build a resizable panel group:
Resizable (root)
├── Resizable.Panel
├── Resizable.Handle
└── Resizable.Panel
Examples
Vertical
Use direction="vertical" for vertical resizing.
import ilha from "ilha";
import { Resizable } from "areia";
export default ilha.render(() => (
<Resizable
direction="vertical"
class="h-64 max-w-md rounded-lg border border-areia-border"
>
<Resizable.Panel defaultSize={25}>
<div class="flex items-center justify-center p-4">
<span class="font-semibold">Header</span>
</div>
</Resizable.Panel>
<Resizable.Handle />
<Resizable.Panel defaultSize={75}>
<div class="flex items-center justify-center p-4">
<span class="font-semibold">Content</span>
</div>
</Resizable.Panel>
</Resizable>
));With Handle
Use withHandle on Resizable.Handle to show a visible grip indicator.
import ilha from "ilha";
import { Resizable } from "areia";
export default ilha.render(() => (
<Resizable
direction="horizontal"
class="h-64 max-w-md rounded-lg border border-areia-border"
>
<Resizable.Panel defaultSize={25}>
<div class="flex items-center justify-center p-4">
<span class="font-semibold">Sidebar</span>
</div>
</Resizable.Panel>
<Resizable.Handle withHandle />
<Resizable.Panel defaultSize={75}>
<div class="flex items-center justify-center p-4">
<span class="font-semibold">Content</span>
</div>
</Resizable.Panel>
</Resizable>
));Nested Panels
Resizable panels can be nested for complex layouts.
import ilha from "ilha";
import { Resizable } from "areia";
export default ilha.render(() => (
<Resizable
direction="horizontal"
class="h-64 max-w-md rounded-lg border border-areia-border"
>
<Resizable.Panel defaultSize={50}>
<div class="flex items-center justify-center p-4">
<span class="font-semibold">One</span>
</div>
</Resizable.Panel>
<Resizable.Handle withHandle />
<Resizable.Panel defaultSize={50}>
<Resizable direction="vertical">
<Resizable.Panel defaultSize={25}>
<div class="flex items-center justify-center p-4">
<span class="font-semibold">Two</span>
</div>
</Resizable.Panel>
<Resizable.Handle withHandle />
<Resizable.Panel defaultSize={75}>
<div class="flex items-center justify-center p-4">
<span class="font-semibold">Three</span>
</div>
</Resizable.Panel>
</Resizable>
</Resizable.Panel>
</Resizable>
));API Reference
Resizable (ResizablePanelGroup)
Root component that manages the resizable group state.
| Prop | Type | Default | Description |
|---|---|---|---|
children | unknown | — | Alternating panels and handles. |
direction | "horizontal" | "vertical" | "horizontal" | Layout axis. |
keyboardResizeBy | number | 10 | Amount (in %) to resize by per keyboard arrow press. |
onLayoutChange | (layout: number[]) => void | — | Called whenever the layout changes, with sizes as percentages. |
Resizable.Panel
Individual panel within a resizable group.
| Prop | Type | Default | Description |
|---|---|---|---|
children | unknown | — | Panel content. |
defaultSize | number | — | Initial size as a percentage. |
minSize | number | 0 | Minimum size as a percentage. |
maxSize | number | 100 | Maximum size as a percentage. |
collapsible | boolean | false | Whether the panel can be collapsed. |
collapsedSize | number | 0 | Size when collapsed as a percentage. |
Resizable.Handle
Resize handle between two panels.
| Prop | Type | Default | Description |
|---|---|---|---|
withHandle | boolean | false | Whether to show a visible grip indicator. |
children | unknown | — | Custom handle content. |
Slots
Resizable renders these data slots:
resizableresizable-panelresizable-handle
The library sets state attributes on parts:
| Attribute | Element | Description |
|---|---|---|
data-direction | all parts | "horizontal" or "vertical". |
data-state | panel | "expanded" or "collapsed". |
data-collapsed | panel | Present when panel is collapsed. |
data-expanded | panel | Present when panel is expanded. |
data-active | handle | "pointer" during drag, "keyboard" when focused. |
Events
Outbound
| Event | Detail | Description |
|---|---|---|
resizable:change | { layout: number[] } | Fired when the layout changes. |
resizable:dragging | { dragging: boolean } | Fired when dragging starts or ends. |
Inbound
| Event | Detail | Description |
|---|---|---|
resizable:set | { layout: number[] } | Set the layout programmatically. |