The Drag Handle
The drag handle is the only interactive element you control directly. useSplitView doesn’t render it — it just gives you a bundle of event handlers via handleProps that you spread onto whatever element you build.
What handleProps does
Section titled “What handleProps does”handleProps contains six event handlers:
{ onPointerDown: (e: React.PointerEvent) => void onPointerMove: (e: React.PointerEvent) => void onPointerUp: (e: React.PointerEvent) => void onPointerCancel: (e: React.PointerEvent) => void onMouseEnter: () => void onMouseLeave: () => void}When you spread them onto an element, you get:
- Pointer capture — the handle tracks the cursor even when it leaves the element, so fast drags don’t lose sync
- Automatic zoom/pan lock on
pointerdown— wheel and pinch gestures are suspended during the drag to prevent conflicts - Hover lock via
onMouseEnter/onMouseLeave— zoom/pan lock while the cursor is idle over the handle, even before a drag starts - Correct
splitupdates based on pointer position in the container, clamped to[0, 100] - Touch-safe — pointer events handle both mouse and touch uniformly, and
stopPropagationprevents pan-under-handle
Minimal handle
Section titled “Minimal handle”A plain 2-pixel vertical line is enough:
<div {...handleProps} style={{ position: "absolute", top: 0, bottom: 0, left: `${split}%`, width: 24, // wider hit area transform: "translateX(-50%)", cursor: "col-resize", zIndex: 10, }}> <div style={{ width: 2, height: "100%", margin: "0 auto", backgroundColor: "white", boxShadow: "0 0 4px rgba(0,0,0,0.5)", }} /></div>Handle with a grip circle
Section titled “Handle with a grip circle”<div {...handleProps} style={{ position: "absolute", top: 0, bottom: 0, left: `${split}%`, width: 32, transform: "translateX(-50%)", cursor: "col-resize", zIndex: 10, display: "flex", alignItems: "center", justifyContent: "center", }}> <div style={{ position: "absolute", top: 0, bottom: 0, width: 2, background: "white", boxShadow: "0 0 8px rgba(0,0,0,0.5)", }} /> <div style={{ width: 32, height: 32, borderRadius: "50%", background: "white", boxShadow: "0 2px 12px rgba(0,0,0,0.4)", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 14, }} > ⇔ </div></div>Vertical direction
Section titled “Vertical direction”Same idea, rotated:
<div {...handleProps} style={{ position: "absolute", left: 0, right: 0, top: `${split}%`, height: 24, transform: "translateY(-50%)", cursor: "row-resize", zIndex: 10, }}> <div style={{ width: "100%", height: 2, background: "white", boxShadow: "0 0 4px rgba(0,0,0,0.5)", }} /></div>Animating the handle position
Section titled “Animating the handle position”split is plain state, so you can use CSS transitions on the left / top property:
<div {...handleProps} style={{ position: "absolute", top: 0, bottom: 0, left: `${split}%`, width: 24, transform: "translateX(-50%)", transition: isDraggingRef.current ? "none" : "left 0.3s ease-out", cursor: "col-resize", }}/>Programmatically moving the handle
Section titled “Programmatically moving the handle”Use setSplit(value) from the hook return:
const { setSplit } = useSplitView()
<button onClick={() => setSplit(50)}>Center</button><button onClick={() => setSplit(0)}>Full After</button><button onClick={() => setSplit(100)}>Full Before</button>Disabling the handle
Section titled “Disabling the handle”To disable split dragging entirely but keep zoom/pan, don’t render the handle (or render a non-interactive copy without handleProps spread) and use setSplit to control the split programmatically. The view state comes from the panes themselves — dragging inside a pane still pans and zooms as usual.