Skip to content
Areia
Esc
navigateopen⌘Jpreview
On this page

Resizable

Accessible resizable panel groups and layouts with keyboard support.

Accessible resizable panel groups with keyboard support. Built on the @areia/slots resizable primitive.

Source Code

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.

Left
Right
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.

Left
Right
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.

Header
Content
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.

Sidebar
Content
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.

One
Two
Three
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:

  • resizable
  • resizable-panel
  • resizable-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.

Was this page helpful?