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.
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
import { Resizable } from "@areia/slots";
Usage
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".
<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.
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-growfrom the current percentage layout. The root element receivesdisplay: flexwithflex-directiondetermined bydirection(horizontal →row, vertical →column). - Drag resize: Handles support mouse drag to resize adjacent panels. Dragging is constrained by panel
data-min-sizeanddata-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
keyboardResizeBypercent.Homesnaps to the previous panel’s minimum size;Endsnaps to its maximum size. - Collapsible panels: Panels with
data-collapsiblecan be collapsed to theirdata-collapsed-sizevia 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.