Resizable
Drag the divider between regions to resize—handy for split editors, side-by-side detail, or stacked header and content.
Use ResizablePanelGroup, ResizablePanel, and ResizableHandle when you want people to adjust how much space each section gets. Wrap your app in ThemeProvider so split layouts respect right-to-left languages; handles pick up neutral dividers and clear hover and focus styles from the experience system.
Installation
The components are exported from @by/experience-system. Add the package with your package manager:
pnpm add @by/experience-systemIn this monorepo, depend on the workspace package (for example via workspace:* or your catalog) so imports resolve to packages/experience-system.
Composition
Use the following composition to build a resizable layout:
ResizablePanelGroup
├── ResizablePanel
├── ResizableHandle
└── ResizablePanelThe underlying primitive is react-resizable-panels (v4). ResizablePanelGroup wraps Group, ResizablePanel wraps Panel, and ResizableHandle wraps Separator. Because this is a non-Radix component, the API tables below mirror the upstream library exports, with the Experience System additions (divider, withHandle, defaulted dir from ThemeProvider) called out inline.
Usage
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from '@by/experience-system';These pieces run on the client ('use client'). In the Next.js App Router, use them inside a client component or load them with a dynamic import. Put ThemeProvider above the tree so the group knows the page direction for resizing in RTL.
<div className="mx-auto w-[400px] shrink-0">
<ResizablePanelGroup className="min-h-[200px] w-full rounded-sm border border-neutral-alpha-3">
<ResizablePanel defaultSize="50%">
<div className="flex h-full items-center justify-center p-6">Left</div>
</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize="50%">
<div className="flex h-full items-center justify-center p-6">Right</div>
</ResizablePanel>
</ResizablePanelGroup>
</div>Examples
Vertical
Set orientation="vertical" to stack regions top and bottom—often a slim header over the main area.
With cards
Compose ResizablePanel with Card content for a master/detail layout where each side is its own surface.
Divider appearance
ResizableHandle controls how obvious the separator is. Use divider="invisible" for a minimal split—the track stays transparent until you hover or focus it (good for dense layouts).
Use withHandle when you want a clear grip bar on top of the default line so people can spot the drag target faster.
API Reference
Pieces map to react-resizable-panels (v4) primitives. The tables below mirror the upstream API in full, with Experience System extensions labeled inline. Standard HTMLDivElement attributes are also accepted on each component (className, style, id, data-*, etc.).
ResizablePanelGroup
ResizablePanelGroup wraps the upstream Group. Direction is inferred from ThemeProvider via dir; default orientation is "horizontal".
| Prop | Type | Default |
|---|---|---|
orientation | "horizontal" | "vertical" | "horizontal" |
defaultLayout | Layout (Record<string, number>) | No default value |
disableCursor | boolean | false |
disabled | boolean | false |
elementRef | Ref<HTMLDivElement | null> | No default value |
groupRef | Ref<GroupImperativeHandle | null> | No default value |
id | string | number | useId() |
onLayoutChange | (layout: Layout) => void | No default value |
onLayoutChanged | (layout: Layout) => void | No default value |
resizeTargetMinimumSize | { coarse: number; fine: number } | Library default |
style | CSSProperties | No default value |
| Data attribute | Values |
|---|---|
[data-slot] | "resizable-panel-group" (experience system) |
[data-panel-group-orientation] | "horizontal" | "vertical" (experience system) |
[data-group] | The resolved id (library) |
display, flex-direction, flex-wrap, and overflow cannot be overridden via style per the upstream library contract.
ResizablePanel
ResizablePanel wraps the upstream Panel. Sizes accept percentages ("50%" or 50), pixels ("240px"), or relative units ("1rem", "30vh", etc.).
| Prop | Type | Default |
|---|---|---|
collapsedSize | number | string | 0 |
collapsible | boolean | false |
defaultSize | number | string | Auto-assigned across panels |
disabled | boolean | false |
elementRef | Ref<HTMLDivElement | null> | No default value |
groupResizeBehavior | "preserve-relative-size" | "preserve-pixel-size" | "preserve-relative-size" |
id | string | number | useId() |
maxSize | number | string | 100 (%) |
minSize | number | string | 0 |
onResize | (size: PanelSize, id, prevSize) => void | No default value |
panelRef | Ref<PanelImperativeHandle | null> | No default value |
style | CSSProperties | No default value |
| Data attribute | Values |
|---|---|
[data-slot] | "resizable-panel" (experience system) |
[data-panel] | The resolved id (library) |
ResizableHandle
ResizableHandle wraps the upstream Separator and adds divider plus withHandle for Experience System styling.
| Prop | Type | Default |
|---|---|---|
disabled | boolean | false |
disableDoubleClick | boolean | false |
elementRef | Ref<HTMLDivElement> | No default value |
id | string | number | useId() |
style | CSSProperties | No default value |
divider | "visible" | "invisible" | "visible" (experience system) |
withHandle | boolean | false (experience system) |
| Data attribute | Values |
|---|---|
[data-slot] | "resizable-handle" (experience system) |
[data-separator] | The resolved id (library) |
[role] | "separator" (library) |
[aria-orientation] | "horizontal" | "vertical" |
flex-grow and flex-shrink cannot be overridden via style per the upstream library contract.
Accessibility
People can move focus to a separator and use the keyboard to resize where the underlying behavior allows it; focus is shown with the accent ring. Put real labels or headings inside panels when the split carries meaning for screen readers, and prefer withHandle for touch surfaces so the drag target is easier to find.
Keyboard interactions
| Key | Description |
|---|---|
Tab / Shift + Tab | Moves focus to or away from a ResizableHandle. |
ArrowLeft / ArrowRight | Resizes a horizontal split when the handle is focused. |
ArrowUp / ArrowDown | Resizes a vertical split when the handle is focused. |
Home / End | Snaps the focused split to its minimum or maximum size (when supported by the upstream library). |
Enter | Toggles a collapsible panel between its expanded and collapsed sizes. |
Source in the repo: packages/experience-system/src/components/Resizable/Resizable.tsx. Agent-oriented contracts: packages/experience-system/src/components/Resizable/Resizable.instructions.md.