Experience System
Components

Resizable

Drag the divider between regions to resize—handy for split editors, side-by-side detail, or stacked header and content.

One
Two
Three

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-system

In 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
└── ResizablePanel

The 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.

Header
Content

With cards

Compose ResizablePanel with Card content for a master/detail layout where each side is its own surface.

One
Two
Three

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).

Left
Right

Use withHandle when you want a clear grip bar on top of the default line so people can spot the drag target faster.

Left
Right

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".

PropTypeDefault
orientation"horizontal" | "vertical""horizontal"
defaultLayoutLayout (Record<string, number>)No default value
disableCursorbooleanfalse
disabledbooleanfalse
elementRefRef<HTMLDivElement | null>No default value
groupRefRef<GroupImperativeHandle | null>No default value
idstring | numberuseId()
onLayoutChange(layout: Layout) => voidNo default value
onLayoutChanged(layout: Layout) => voidNo default value
resizeTargetMinimumSize{ coarse: number; fine: number }Library default
styleCSSPropertiesNo default value
Data attributeValues
[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.).

PropTypeDefault
collapsedSizenumber | string0
collapsiblebooleanfalse
defaultSizenumber | stringAuto-assigned across panels
disabledbooleanfalse
elementRefRef<HTMLDivElement | null>No default value
groupResizeBehavior"preserve-relative-size" | "preserve-pixel-size""preserve-relative-size"
idstring | numberuseId()
maxSizenumber | string100 (%)
minSizenumber | string0
onResize(size: PanelSize, id, prevSize) => voidNo default value
panelRefRef<PanelImperativeHandle | null>No default value
styleCSSPropertiesNo default value
Data attributeValues
[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.

PropTypeDefault
disabledbooleanfalse
disableDoubleClickbooleanfalse
elementRefRef<HTMLDivElement>No default value
idstring | numberuseId()
styleCSSPropertiesNo default value
divider"visible" | "invisible""visible" (experience system)
withHandlebooleanfalse (experience system)
Data attributeValues
[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

KeyDescription
Tab / Shift + TabMoves focus to or away from a ResizableHandle.
ArrowLeft / ArrowRightResizes a horizontal split when the handle is focused.
ArrowUp / ArrowDownResizes a vertical split when the handle is focused.
Home / EndSnaps the focused split to its minimum or maximum size (when supported by the upstream library).
EnterToggles 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.