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).
A two-pane split with a draggable boundary. defaultSizes seeds the panes'
percentages; drag the handle to resize.
orientation="vertical" splits into stacked, resizable rows.
Nest a vertical SplitView inside a pane of a horizontal one to get resizable columns and
rows.
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.
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.
Constrain how far a pane can be resized with minSize / maxSize (pixels or percentages).
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.
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.
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.
useStoredSplitViewThe 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.
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.
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.
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).
PlumaSplitView extends BoxInitial 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.
PlumaSplitViewPane extends BoxInitial 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.
PlumaSplitViewResizer extends BoxDefault:false
Disables this specific resizer.
Default:true
Renders a visible grip affordance in the center of the resizer.