Resizable

Split a surface into panels the user can drag to resize. No Radix primitive ships this, so the handle is hand-rolled: it carries role=separator, is arrow-key operable, and reports through aria-valuenow.

Sidebar
Editor
Preview

Installation#

# One-time setup (tokens + lib helpers)npx koalaui-cli@latest init# Add this component (its dependencies come along)npx koalaui-cli@latest add resizable

Usage#

usage.tsxTSX
import {  ResizablePanelGroup,  ResizablePanel,  ResizableHandle,} from "@/components/ui/resizable"export function Example() {  return (    <ResizablePanelGroup direction="horizontal" className="h-64 rounded-lg border">      <ResizablePanel defaultSize={60}>One</ResizablePanel>      <ResizableHandle withHandle />      <ResizablePanel defaultSize={40}>Two</ResizablePanel>    </ResizablePanelGroup>  )}

Horizontal#

The default direction lays panels side-by-side. Give each panel a defaultSize weight (conventionally summing to 100), then drag the divider to redistribute the space between the two panels it sits between.

Left
Right

Vertical#

Set direction="vertical" to stack panels and drag a horizontal divider. The handle, cursor, and keyboard axis all flip automatically.

Chart
Table

With handle#

Add withHandle to render a minimal, fully-rounded grip pill: a clear "drag me" affordance for wide gutters. Without it the divider is a hairline that highlights on hover and focus.

Grip
Hairline
divider

Custom handle#

Pass children to a ResizableHandle to swap the visual entirely (an icon nub, a brand chip, your own shape) while keeping all the drag, keyboard, and ARIA behavior. The two examples below show a dotted chip and a bold brand pill.

Dotted chip
Brand pill
Done

Min & max sizes#

Clamp how far a panel can be dragged with minSize and maxSize. A number (or "30%") is a proportional weight; a pixel string like "240px" is a hard bound pinned as a real CSS min/max-width, so it holds at any group size: drag it, then shrink your window and the sidebar below keeps its 240px–360px range instead of scaling away.

240px–360px
min 320px

Collapsible#

Mark a panel collapsible and it snaps shut once dragged past half its minSize, settling at collapsedSize (default 0): the pattern for a sidebar that tucks away. Drag back out to restore it.

Collapsible
Content

Nested dashboard#

Groups nest: put a vertical ResizablePanelGroup inside a panel of a horizontal one to build a real dashboard split. Each group resizes independently.

Navigation
Overview
Activity

API reference#

ResizablePanelGroup

The split container. Forwards every div prop.

  • direction: "horizontal" | "vertical". Lay panels side-by-side or stacked. Default "horizontal".
  • onLayout: (sizes: number[]) => void. Fires with the panel weights (DOM order) whenever the layout changes.
  • className: sizes the box; panels fill it. Give it a height (and usually a border/radius).

ResizablePanel

One region of the split. Forwards every div prop.

  • defaultSize: number. Starting weight (conventionally a percentage). Omit on every panel for an equal split.
  • minSize / maxSize: number | string. Clamp the drag range. A number/"%" is a proportional weight; "240px" is a hard width/height bound that holds on window resize.
  • collapsible: boolean. Snap shut past half minSize.
  • collapsedSize: number | string. Size when collapsed. Default 0.
  • asChild: boolean. Render as the child element via Radix Slot.

ResizableHandle

The draggable divider. role="separator", arrow-key operable (Shift = coarse, Home/End jump to min/max). Forwards every div prop.

  • withHandle: boolean. Show the minimal rounded grip pill.
  • children: ReactNode. Custom handle content, centered on the divider, replaces the default pill while keeping all behavior.
  • disabled: boolean. Disable dragging and keyboard resizing.

FAQ#

Give each ResizablePanel a `defaultSize` weight, conventionally summing to 100. Omit it on every panel for an equal split.