A layout component for splitting a space into resizable, nestable panes with draggable handles.

SplitView lays out resizable panes along one axis. Place a SplitViewResizer between two SplitViewPanes to make that boundary draggable. Nest a SplitView inside a pane for two-dimensional (columns and rows) layouts.

The group must have a height for the panes to fill — set one on the SplitView (or a parent).

Importing

import { SplitView, SplitViewPane, SplitViewResizer } from '@customerio/pluma-components/react';

Basic usage

A two-pane split with a draggable boundary. defaultSizes seeds the panes' percentages; drag the handle to resize.

Loading editor

Orientation

orientation="vertical" splits into stacked, resizable rows.

Loading editor

Two-dimensional layouts

Nest a vertical SplitView inside a pane of a horizontal one to get resizable columns and rows.

Loading editor

Sizing panes in pixels

A pane's size can be written as a percentage ('30', '30%') or as a pixel length (120, '120px', '12rem'). The unit only says how you wrote the value: every pane holds a share of the split, and every pane stays resizable. A pixel defaultSize is where the pane starts; it becomes a share of the available space once the split is measured, and the panes that asked for nothing split what is left.

Loading editor

Keeping a pane at a fixed pixel size

By default a pane keeps its share of the split, so it grows and shrinks with the container. For a sidebar or toolbar that should hold its pixel width while the rest of the layout reflows, set groupResizeBehavior="preserve-pixel-size". The pane is still resizable — this only controls what happens when the split itself changes size. Give the split at least one preserve-relative-size pane (the default) to absorb the difference.

To stop a pane being resized at all, use isResizable={false} — that is a separate concern from how its size is written.

Loading editor

Minimum and maximum sizes

Constrain how far a pane can be resized with minSize / maxSize (pixels or percentages).

Loading editor

Hiding panes

Set isHidden to collapse a pane to zero and let the remaining panes take its space; the pane stays mounted and restores its size when shown again. Deciding when to hide (and rendering any toggle affordance) is up to you.

Render resizers between every pair of panes and leave them there — hiding a pane sorts the dividers out on its own. A hidden pane takes no space, so the panes on either side of it become neighbors and the boundary they now share stays draggable; whichever resizer is left with nothing to separate collapses instead of painting a divider that cannot move.

Loading editor

Snapping

snaps are percentages the boundary is pinned to. When set, dragging a resizer moves discretely between these points (snapping to the nearest one) instead of resizing smoothly — useful for offering a few preset splits.

Loading editor

Persisting sizes

SplitView is uncontrolled by default. To persist a user's layout, read the sizes from onResizeEnd (fired once a drag or keyboard change is committed) and store them, then pass them back as defaultSizes. For full control, pass sizes and update it from onSizesChange.

useStoredSplitView

The useStoredSplitView hook wires that persistence up for you. Give it an id and spread the result onto the SplitView — the layout is saved to localStorage on resize and restored on the next load. Resize the example below and reload the page: it comes back the way you left it.

Loading editor

Custom storage — by default the hook uses localStorage (falling back to a no-op when it's unavailable, e.g. during server rendering). Pass a storage object ({ getItem, setItem }) to persist somewhere else — cookies for SSR, or an in-memory stub for tests.

Conditional panels

When panels are shown and hidden, give each SplitViewPane a stable id and pass the currently visible ids as panelIds. Each configuration then remembers its own layout, and the split re-applies the stored layout for whichever configuration is active — no remounting required. Resize a boundary, toggle the preview off and on, and the layout comes back.

Loading editor

Accessibility

Each SplitViewResizer is a role="separator" with aria-orientation and aria-valuenow/aria-valuemin/aria-valuemax. It is keyboard-operable: focus the handle and use the arrow keys to resize (hold Shift for a coarser step), following the WAI-ARIA window splitter pattern. When snaps is set, the arrow keys move between the snap points instead (Shift / Home / End jump to the ends).

API

Initial percentages for the panes, in DOM order, for the uncontrolled case. When omitted, panes split the available space equally.

Default:true

Disables resizing for the entire split. Panes still reflow when hidden.

Fires continuously while a resizer is being dragged, with the live pane percentages.

Fires once a resize gesture is committed (pointer up / keyboard change). Use this to persist a user's layout.

Fires when the layout changes in the controlled case. Mirror of sizes.

Default:'horizontal'

The axis panes are laid out and resized along. horizontal splits into columns, vertical splits into rows.

Controlled percentages for the panes, in DOM order. When provided the consumer owns the layout and must update it from onSizesChange.

Snap points, as percentages of the space available to panes. When set, dragging a resizer moves the boundary discretely between these points (the nearest one to the pointer) rather than resizing smoothly.

Initial size for the uncontrolled case. See SplitViewPaneSize. A pixel value seeds the pane's starting share of the split; the pane stays resizable, and from then on follows groupResizeBehavior.

Default:'preserve-relative-size'

What happens to this pane when the split view itself grows or shrinks. preserve-pixel-size keeps the pane at its current pixel size — pair it with a pixel size/defaultSize for a sidebar that stays put while the rest of the layout reflows. The pane is still resizable either way. A split needs at least one preserve-relative-size pane to absorb the difference; when none can, every pane scales proportionally instead.

A stable identity for the pane. Used to remember a pane's size across renders and, with useStoredSplitView, to key persisted layouts. Give conditionally-rendered panes an explicit id so each configuration is tracked and restored independently; when omitted, an automatic id is generated. In React it is also forwarded as the element's id.

Default:false

When true, the pane collapses to zero and the remaining panes take its space. The pane stays mounted. Toggling this is the consumer's responsibility.

Default:true

When false, the resizers adjacent to this pane are inert, so a user cannot drag it. The pane still holds a share of the split and reflows with it — to hold a pixel size instead, see groupResizeBehavior.

Maximum size this pane may be resized to. Pixels (number / '200px') or percentage ('20%').

Minimum size this pane may be resized to. Pixels (number / '200px') or percentage ('20%').

Controlled size. See SplitViewPaneSize for how units are interpreted. The consumer owns the value, so a controlled pane holds this size until the consumer changes it — a pixel value is re-resolved against the available space whenever the split is resized.

Default:false

Disables this specific resizer.

Default:true

Renders a visible grip affordance in the center of the resizer.