Skip to content

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

SlotRequiredDescription
resizableYesRoot container. Gets display: flex and directional flex-direction.
resizable-panelYesA resizable panel. Sized via flex-grow as a percentage.
resizable-handleYesA drag/touch/keyboard handle between two adjacent panels.

Panel Constraints

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

AttributeTypeDescription
data-min-sizenumberMinimum size for this pane, as a percentage.
data-max-sizenumberMaximum size for this pane, as a percentage.
data-default-sizenumberDefault size for this pane, as a percentage.
data-collapsiblebooleanWhether the pane can be collapsed via the handle.
data-collapsed-sizenumberSize of the pane when collapsed, as a percentage.

Generated Attributes

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

AttributeElement(s)Description
data-directionRoot, panels, handles"horizontal" or "vertical".
data-stateresizable-panel"expanded" or "collapsed".
data-expandedresizable-panelPresent when the panel is expanded.
data-collapsedresizable-panelPresent when the panel is collapsed.
data-activeresizable-handle"pointer" while dragging, "keyboard" while focused.

Events

Outbound

EventDetailDescription
resizable:change{ layout: number[] }Fired on the root when the pane layout percentages change.

API Reference

Options

OptionTypeDefaultDescription
direction"horizontal" | "vertical""horizontal"Layout direction. Sets flex-direction and determines handle movement axis.
keyboardResizeBynumber10Percentage increment when resizing via keyboard (arrow keys).
onLayoutChange(layout: number[]) => voidCalled when the layout percentages change.

Controller

MemberTypeDescription
layoutnumber[] (getter)Current layout as an array of pane size percentages.
setLayout(l)(layout: number[]) => voidSet the layout to the given array of percentages.
resizePane(i, s)(index: number, size: number) => voidResize the pane at index to size percent.
collapse(i)(index: number) => voidCollapse the pane at index.
expand(i)(index: number) => voidExpand the pane at index.
isCollapsed(i)(index: number) => booleanReturns true if the pane at index is collapsed.
isExpanded(i)(index: number) => booleanReturns true if the pane at index is expanded.
getSize(i)(index: number) => numberReturns the current size of the pane at index as a percentage.
destroy()() => voidRemove 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-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.