ResizablePanels

Two panes split by a divider you drag or move with the arrow keys.

Import

ts
import { ResizablePanels } from '@k8ordo/ui';

Usage

value is the first pane’s share in percent; the second pane takes the rest. The root fills its parent, so give the parent a size (a vertical split needs a height). The divider takes focus; the arrow keys move it the way they point on screen, and Home / End jump to min / max. In a right-to-left page the first pane sits on the right, so ArrowLeft widens it. The panes follow the line of text, so in vertical writing mode orientation="horizontal" stacks them.

Sidebar

Content

tsx
<div className="h-48">
  <ResizablePanels.Root defaultValue={30}>
    <ResizablePanels.Panel>Sidebar</ResizablePanels.Panel>
    <ResizablePanels.Handle />
    <ResizablePanels.Panel>Content</ResizablePanels.Panel>
  </ResizablePanels.Root>
</div>

Vertical

orientation="vertical" stacks the two panes. The divider moves with the up and down arrow keys, and its value is the top pane’s share of the height.

Editor

Terminal

tsx
<div className="h-48">
  <ResizablePanels.Root orientation="vertical">
    <ResizablePanels.Panel>Editor</ResizablePanels.Panel>
    <ResizablePanels.Handle />
    <ResizablePanels.Panel>Terminal</ResizablePanels.Panel>
  </ResizablePanels.Root>
</div>

Naming the divider

The divider’s value is the first pane’s size, so name it after the first pane: point aria-labelledby at its heading. Without one it falls back to resizablePanelsHandle from the dictionary.

Files

Preview

tsx
<ResizablePanels.Root defaultValue={30}>
  <ResizablePanels.Panel>
    <h2 id="files-heading">Files</h2>
  </ResizablePanels.Panel>
  <ResizablePanels.Handle aria-labelledby="files-heading" />
  <ResizablePanels.Panel>Preview</ResizablePanels.Panel>
</ResizablePanels.Root>

Controlled

onChange receives the share while dragging, too. From the keyboard it moves by step.

Sidebar: 30%

Sidebar

Content

tsx
const [size, setSize] = useState(30);

<ResizablePanels.Root onChange={setSize} value={size}>
  …
</ResizablePanels.Root>

Props

ResizablePanels.Root

children?
Type: ReactNode
Default: -
defaultValue?
Type: number
Default: 50
max?
Type: number
Default: 90
min?
Type: number
Default: 10
onChange?
Type: (value: number) => void
Default: -
orientation?
Type: 'horizontal'|'vertical'
Default: 'horizontal'
ref?
Type: Ref<HTMLDivElement>
Default: -
step?
Type: number
Default: 5
value?
Type: number
Default: -

Type base (some attrs are managed internally): HTMLAttributes<HTMLDivElement>

ResizablePanels.Panel

children?
Type: ReactNode
Default: -
ref?
Type: Ref<HTMLDivElement>
Default: -

Type base (some attrs are managed internally): HTMLAttributes<HTMLDivElement>

ResizablePanels.Handle

Type base (some attrs are managed internally): HTMLAttributes<HTMLDivElement>

Wording this component renders (labels, placeholders, and the like) comes from the message dictionary when no prop sets it. To change it, see: i18n