---
title: Resizable
description: "A split-panel primitive that lets users resize adjacent panels with drag, touch, and keyboard controls."
order: 16
tags: [primitives]
---

# Resizable

A primitive for building resizable split-panel layouts. Users can drag or touch-resize handles between panels, use keyboard arrows and Home/End keys for fine adjustments, and collapse or expand individual panels. Each panel is sized with flex-grow percentages, and panel constraints are declared via HTML data attributes.

```ts
import { Resizable } from "@areia/slots";

document.querySelector("#root")!.innerHTML = `
  <div data-slot="resizable" class="win95-inset flex h-32 w-96 bg-white">
    <div data-slot="resizable-panel" data-default-size="50" class="flex items-center justify-center p-3">
      Left panel content
    </div>
    <div data-slot="resizable-handle" class="w-2 cursor-col-resize border-x border-black bg-[#c0c0c0]"></div>
    <div data-slot="resizable-panel" data-default-size="50" class="flex items-center justify-center p-3">
      Right panel content
    </div>
  </div>
`;

const root = document.querySelector('[data-slot="resizable"]')!;
const controller = Resizable.createResizable(root, {
  direction: "horizontal",
  keyboardResizeBy: 10,
});
```

## Import

```ts
import { Resizable } from "@areia/slots";
```

## Usage

```ts
import { Resizable } from "@areia/slots";

// Auto-bind a single root
const root = document.querySelector('[data-slot="resizable"]');
const controller = Resizable.createResizable(root, {
  direction: "horizontal",
  keyboardResizeBy: 10,
  onLayoutChange: (layout) =>
    console.log("layout changed:", layout),
});

// Auto-bind all unbound roots in scope
const controllers = Resizable.create();
```

## Expected Markup

The resizable expects alternating panel and handle elements inside the root. Each panel gets `data-slot="resizable-panel"` and each handle gets `data-slot="resizable-handle"`.

```html
<div data-slot="resizable">
  <div data-slot="resizable-panel" data-default-size="50">
    Left panel content
  </div>
  <div data-slot="resizable-handle"></div>
  <div data-slot="resizable-panel" data-default-size="50">
    Right panel content
  </div>
</div>
```

### Data Slots

| Slot               | Required | Description                                                            |
| ------------------ | -------- | ---------------------------------------------------------------------- |
| `resizable`        | Yes      | Root container. Gets `display: flex` and directional `flex-direction`. |
| `resizable-panel`  | Yes      | A resizable panel. Sized via flex-grow as a percentage.                |
| `resizable-handle` | Yes      | A drag/touch/keyboard handle between two adjacent panels.              |

### Panel Constraints

Each `resizable-panel` supports the following data attributes for size constraints and collapsible behavior:

| Attribute             | Type      | Description                                       |
| --------------------- | --------- | ------------------------------------------------- |
| `data-min-size`       | `number`  | Minimum size for this pane, as a percentage.      |
| `data-max-size`       | `number`  | Maximum size for this pane, as a percentage.      |
| `data-default-size`   | `number`  | Default size for this pane, as a percentage.      |
| `data-collapsible`    | `boolean` | Whether the pane can be collapsed via the handle. |
| `data-collapsed-size` | `number`  | Size of the pane when collapsed, as a percentage. |

### Generated Attributes

The controller writes the following attributes on initialization and updates them as state changes:

| Attribute        | Element(s)            | Description                                             |
| ---------------- | --------------------- | ------------------------------------------------------- |
| `data-direction` | Root, panels, handles | `"horizontal"` or `"vertical"`.                         |
| `data-state`     | `resizable-panel`     | `"expanded"` or `"collapsed"`.                          |
| `data-expanded`  | `resizable-panel`     | Present when the panel is expanded.                     |
| `data-collapsed` | `resizable-panel`     | Present when the panel is collapsed.                    |
| `data-active`    | `resizable-handle`    | `"pointer"` while dragging, `"keyboard"` while focused. |

## Events

### Outbound

| Event              | Detail                 | Description                                                |
| ------------------ | ---------------------- | ---------------------------------------------------------- |
| `resizable:change` | `{ layout: number[] }` | Fired on the root when the pane layout percentages change. |

## API Reference

### Options

| Option             | Type                         | Default        | Description                                                                  |
| ------------------ | ---------------------------- | -------------- | ---------------------------------------------------------------------------- |
| `direction`        | `"horizontal" \| "vertical"` | `"horizontal"` | Layout direction. Sets `flex-direction` and determines handle movement axis. |
| `keyboardResizeBy` | `number`                     | `10`           | Percentage increment when resizing via keyboard (arrow keys).                |
| `onLayoutChange`   | `(layout: number[]) => void` | —              | Called when the layout percentages change.                                   |

### Controller

| Member             | Type                                    | Description                                                      |
| ------------------ | --------------------------------------- | ---------------------------------------------------------------- |
| `layout`           | `number[]` (getter)                     | Current layout as an array of pane size percentages.             |
| `setLayout(l)`     | `(layout: number[]) => void`            | Set the layout to the given array of percentages.                |
| `resizePane(i, s)` | `(index: number, size: number) => void` | Resize the pane at `index` to `size` percent.                    |
| `collapse(i)`      | `(index: number) => void`               | Collapse the pane at `index`.                                    |
| `expand(i)`        | `(index: number) => void`               | Expand the pane at `index`.                                      |
| `isCollapsed(i)`   | `(index: number) => boolean`            | Returns `true` if the pane at `index` is collapsed.              |
| `isExpanded(i)`    | `(index: number) => boolean`            | Returns `true` if the pane at `index` is expanded.               |
| `getSize(i)`       | `(index: number) => number`             | Returns the current size of the pane at `index` as a percentage. |
| `destroy()`        | `() => void`                            | Remove all event listeners and clean up.                         |

## Controller

The controller is returned by `Resizable.createResizable()` and provides imperative control over pane sizes and collapse state.

```ts
const controller = Resizable.createResizable(root, {
  direction: "horizontal",
});

// Read current layout
console.log(controller.layout); // [50, 50]

// Set all pane sizes at once
controller.setLayout([33, 33, 34]);

// Resize a single pane
controller.resizePane(0, 40);

// Collapse a pane
controller.collapse(0);

// Expand a pane
controller.expand(0);

// Check pane state
console.log(controller.isCollapsed(0)); // true | false
console.log(controller.isExpanded(0)); // true | false

// Get a pane's current size
console.log(controller.getSize(0)); // percentage

// Tear down
controller.destroy();
```

### Behavior

- **Flex sizing**: Each panel is sized via `flex-grow` from the current percentage layout. The root element receives `display: flex` with `flex-direction` determined by `direction` (horizontal → `row`, vertical → `column`).
- **Drag resize**: Handles support mouse drag to resize adjacent panels. Dragging is constrained by panel `data-min-size` and `data-max-size`.
- **Touch resize**: Handles support touch events for resizing on touch devices.
- **Keyboard resize**: When a handle is focused, arrow keys adjust adjacent panels by `keyboardResizeBy` percent. `Home` snaps to the previous panel’s minimum size; `End` snaps to its maximum size.
- **Collapsible panels**: Panels with `data-collapsible` can be collapsed to their `data-collapsed-size` via keyboard Enter on the adjacent handle or via the controller API.

## ilha morph

When used through Areia’s styled `Resizable`, panel/root inline styles are morph-safe by default (`data-morph-preserve="style"`). Dragged sizes survive parent island re-renders without app-level preserve attributes.
