Skip to content

Resizable Split Pane

A split view with pointer capture, pixel minimums resolved against real width, and a separator you can actually tab to.

DOM + CSS0 deps1 file · 5 KB TSX

Prominimalbrutalist
Preview

Props

46
140
2

The source is part of Pro

$27/month or $179 once. See Pro

No runtime dependencies
"use client";

import { useRef, useState, type ReactNode } from "react";

export function ResizableSplitPane({
  first,
  second,
  initial = 46,
  minPx = 140,
  orientation = "horizontal",
  step = 2,
  onResize,
}: {
  first?: ReactNode;
  second?: ReactNode;
  /** Starting size of the first pane, as a percentage. */
  initial?: number;
  /** Minimum size of EITHER pane, in pixels. */

  // ---------------------------------------------------------------
  // Full source available with a viberdy Pro subscription.
  // https://viberdy.dev/pricing
  // ---------------------------------------------------------------
}
Details

About this component

A split pane is four lines of flexbox and four bugs. The first is that a drag handled with document-level move listeners, or worse with listeners on the handle itself, dies the moment the pointer outruns a six-pixel target — which on any fast drag is immediately; pointer capture fixes it in one line and removes the need for document listeners at all, so there is nothing left to leak. The second is the clamp: minimum sizes are naturally written in pixels while the split itself is a percentage, and clamping the percentage against a fixed number lets a pane collapse under its minimum in a narrow container while refusing to reach it in a wide one, so the bound has to be recomputed from the measured size on every move. The third is that the separator is almost never a real control — here it carries the separator role with a current value, takes focus, moves on arrow keys, jumps to the extremes on Home and End and restores the default on Enter, because a split that can only be dragged is unusable by keyboard. And the fourth is invisible until content gets wide: flex items default to a minimum size of auto, so without an explicit zero minimum a wide child silently refuses to shrink and the panes stop matching the percentage they are told to use.

Curator’s note

Every resizer that only works if you drag slowly is missing setPointerCapture. It is one line.

Pairs well with

Snapping Cursor

components
Pro