bx

Resizable

Accessible resizable panel groups and layouts.

One
Two
Three
tsx
<ResizablePanelGroup direction="horizontal" className="max-w-md rounded-lg border">
  <ResizablePanel defaultSize={50}>
    <div className="flex h-[200px] items-center justify-center p-6"><span className="font-semibold">One</span></div>
  </ResizablePanel>
  <ResizableHandle withHandle />
  <ResizablePanel defaultSize={50}>
    <ResizablePanelGroup direction="vertical">
      <ResizablePanel defaultSize={25}>
        <div className="flex h-full items-center justify-center p-6"><span className="font-semibold">Two</span></div>
      </ResizablePanel>
      <ResizableHandle direction="vertical" />
      <ResizablePanel defaultSize={75}>
        <div className="flex h-full items-center justify-center p-6"><span className="font-semibold">Three</span></div>
      </ResizablePanel>
    </ResizablePanelGroup>
  </ResizablePanel>
</ResizablePanelGroup>

Resizable: side-by-side panels with a handle to adjust their size (VS Code style). The group is horizontal or vertical. Each Panel has a defaultSize (%) and optionally a minSize. Resizing is done by dragging the handle with a mouse, or with the arrow keys when the handle has the focus.

Installation

Terminal
$ bext ui add resizable

Usage

tsx
import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from "@bext-stack/ui/primitives"

<ResizablePanelGroup direction="horizontal" className="max-w-md rounded-lg border">
  <ResizablePanel defaultSize={50}>Left</ResizablePanel>
  <ResizableHandle />
  <ResizablePanel defaultSize={50}>Right</ResizablePanel>
</ResizablePanelGroup>

Anatomy

<ResizablePanelGroup direction="horizontal">
  ├── <ResizablePanel defaultSize={50} minSize={20}>left</ResizablePanel>
  ├── <ResizableHandle />
  └── <ResizablePanel defaultSize={50}>right</ResizablePanel>

API Reference

ResizablePanelGroup

Prop Type Default Description
direction "horizontal" | "vertical" "horizontal" Direction of the split.
children ReactNode — ResizablePanels separated by ResizableHandles.
className string — Extra classes on the group (flex, h-full w-full overflow-hidden).

ResizablePanel

Prop Type Default Description
defaultSize number 50 Initial size in % (0-100), applied as a flex-grow ratio.
minSize number 10 Minimum size in % while dragging.
children ReactNode — Panel content. The panel scrolls when the content overflows.

ResizableHandle

Prop Type Default Description
withHandle boolean false Shows a small visual grip in the center of the handle.
ariaLabel string "Resize panels" Accessible name of the separator.
direction "horizontal" | "vertical" "horizontal" Direction of the parent group, used for the server-rendered aria-orientation. The island corrects it from the group, so this only matters before the island runs.
className string — Extra classes on the 1px separator line.

Data Attributes

Attributes you can target in custom CSS to override the style for each state.

Attribute On Values Description
data-ui="resizable" PanelGroup root — Scope used by the island to measure the group while dragging.
data-direction PanelGroup root "horizontal" | "vertical" Reflects the direction prop. The handle orientation and cursor follow it.
data-ui-rz-handle ResizableHandle — A mousedown on it starts the drag between the previous and the next panel; the arrow keys resize the same two panels.
aria-valuenow / aria-valuemin / aria-valuemax ResizableHandle 0 to 100 Size of the panel before the handle, in percent of the group, and its bounds. Set by the island and updated on every resize.
data-ui-rz-panel ResizablePanel data-min-size="…" Marks a panel. data-min-size carries the minSize prop.

Keyboard

Key Action
Tab Focuses each handle.
Arrow Left / Right Horizontal group: shrinks or grows the panel before the handle by 5% of the group.
Arrow Up / Down Vertical group: same, on the vertical axis.
Home / End Sets the panel before the handle to its minimum / maximum size.

Accessibility

The handle is a focusable role="separator" (tabindex="0") named "Resize panels" (ariaLabel to change it). aria-orientation is perpendicular to the group: "vertical" for a horizontal group. The island sets aria-valuenow, aria-valuemin and aria-valuemax to the size of the panel before the handle, in percent of the group, and updates them after a key press or a drag. These three attributes are absent until the island has run.

The minimum is the minSize of the panel before the handle; the maximum leaves the minSize of the panel after it. There is no touch drag: on a touch screen the panels keep their default size, so make sure every panel is usable at that size.

Try it live
Open in play

Edit this component live in the bext playground. The PRISM + signals version is the bext-native idiom: fine-grained reactivity, no virtual DOM.

src/app/page.tsx 2 files · signals · runs in your browser
Pure PRISM + real bext signals: fine-grained, no re-render.