Resizable Panels
Flexible split layouts with pointer, keyboard, constraint, and collapse controls.
ResizablePanels creates split-view layouts for editors, dashboards, inspectors, navigation rails, and stacked workspaces. Panels can be resized with a pointer or keyboard, constrained with minimum and maximum percentages, persisted in localStorage, and optionally collapsed with state-specific icons.
Basic usage
Each panel requires a unique key and a content node. Panel sizes are percentages and are normalized to a total of 100.
import { ResizablePanels } from '@mosmox/ui';
<ResizablePanels
defaultSizes={[24, 52, 24]}
panels={[
{ key: 'navigation', content: <Navigation /> },
{ key: 'main', content: <MainContent /> },
{ key: 'inspector', content: <Inspector /> },
]}
/>Directions and container sizing
direction="horizontal" is the default and places panels side by side. Use direction="vertical" for stacked regions.
Vertical layouts need a parent with an explicit height. The component fills that height, so the percentage sizes have a real area to divide:
<div style={{ height: 420 }}>
<ResizablePanels
direction="vertical"
defaultSizes={[40, 60]}
panels={[
{ key: 'preview', content: <Preview /> },
{ key: 'logs', content: <Logs /> },
]}
/>
</div>On narrow screens, horizontal layouts switch to a stacked visual layout through the component stylesheet. If precise mobile resizing is required, use direction="vertical" explicitly and provide a sized container.
Initial sizes and constraints
Use defaultSizes for the complete initial layout, or defaultSize for an individual panel. minSize and maxSize are percentages and limit how far each panel can shrink or grow. The constraints are applied to initial values, controlled values, pointer dragging, keyboard resizing, and restored layouts.
<ResizablePanels
defaultSizes={[24, 52, 24]}
panels={[
{ key: 'files', content: <Files />, minSize: 16, maxSize: 40 },
{ key: 'editor', content: <Editor />, minSize: 30 },
{ key: 'preview', content: <Preview />, minSize: 20, maxSize: 50 },
]}
/>When a supplied layout conflicts with its constraints, the component clamps each value and redistributes the remaining percentage across panels with available capacity. A panel's minSize must not exceed its effective maxSize; if it does, the minimum wins.
Controlled and uncontrolled layouts
Without sizes, the component owns its current layout. Use defaultSizes for the initial value. For controlled state, pass sizes and update it from onResize:
const [sizes, setSizes] = useState([30, 70]);
<ResizablePanels
sizes={sizes}
onResize={(nextSizes, changedHandleIndex) => {
setSizes(nextSizes);
console.log('Changed handle:', changedHandleIndex);
}}
panels={[
{ key: 'sidebar', content: <Sidebar /> },
{ key: 'content', content: <Content /> },
]}
/>changedHandleIndex is the zero-based index of the handle that changed. Collapse/expand actions also report the affected panel index. In uncontrolled mode, omitting onResize is valid.
Appearance variants
variant accepts default, card, separated, and minimal:
default— plain panels and handles;card— shared surface, padding, border, and rounded panel cards;separated— separated rounded panels;minimal— transparent editor-style panels with reduced padding.
Use className and style for additional root-level customization.
Handles and pointer resizing
Each boundary is a native button. Drag a handle to resize the two adjacent panels. handleSize controls the handle width in a horizontal layout and height in a vertical layout. handleContent replaces the default visual grip but does not remove the button behavior.
Set showHandle={false} to hide resize handles. This disables pointer and keyboard resizing through the handles, while collapse buttons can still be shown independently.
Keyboard resizing and reset
Focus a handle and use the arrow key for its active direction: Left/Right for horizontal layouts and Up/Down for vertical layouts. step sets the percentage changed per key press. Home and End restore the default layout. Double-clicking a handle also restores the default layout when resetOnDoubleClick is enabled.
Set keyboardResize={false} to disable arrow-key resizing while keeping pointer dragging available.
Persistence
Set storageKey to persist the latest percentage sizes in localStorage. Persistence is opt-in. Invalid or unavailable storage is ignored safely. The stored value contains panel sizes only; collapsed/expanded state is not persisted. In controlled mode, the component emits and writes new sizes but does not replace the parent-controlled sizes value.
<ResizablePanels storageKey="settings-workspace-layout" panels={panels} />Collapsible panels
Mark a panel with collapsible: true and set showCollapseButtons on the root:
const panels = [
{
key: 'sidebar',
content: <Sidebar />,
minSize: 18,
maxSize: 45,
collapsible: true,
collapsedSize: 4,
collapsedMinSize: 34,
},
{ key: 'main', content: <Main /> },
];
<ResizablePanels showCollapseButtons panels={panels} />collapsedSizeis the percentage layout value emitted while collapsed;collapsedMinSizeis the physical minimum size in pixels on the active axis;- values below the default button-safe minimum of
34pxare raised to34px; - the collapsed panel reserves the handle width as well when it has a following resize handle;
- the collapse button is positioned at the top of the collapsed panel, with
4pxside and top spacing; - a collapsed panel cannot be resized through its adjacent handle until expanded.
Customize the icons independently for each state with collapseIcon and expandIcon. If omitted, the component uses its default directional chevrons:
{
key: 'sidebar',
content: <Sidebar />,
collapsible: true,
collapseIcon: <PanelLeftClose aria-hidden="true" />,
expandIcon: <PanelLeftOpen aria-hidden="true" />,
}Accessibility
The root uses aria-label="Resizable panels" by default and accepts a custom aria-label. Each panel can provide ariaLabel. Resize handles expose native button semantics, orientation, minimum, maximum, and current values. Collapse controls expose Collapse … panel and Expand … panel labels. Keep custom icon nodes decorative with aria-hidden="true"; the button's accessible name remains available.
API
ResizablePanels props
| Prop | Type | Default | Description |
|---|---|---|---|
panels | ResizablePanel[] | — | Panels rendered in order. |
direction | 'horizontal' | 'vertical' | 'horizontal' | Split axis. |
sizes | number[] | — | Controlled percentage sizes. |
defaultSizes | number[] | — | Initial uncontrolled percentage sizes. |
onResize | (sizes: number[], handleIndex: number) => void | — | Called after pointer, keyboard, or collapse resizing. |
step | number | 5 | Percentage changed by each keyboard step. |
handleSize | number | 12 | Handle width or height in pixels. |
handleContent | ReactNode | Default grip | Custom handle content. |
showHandle | boolean | true | Shows resize handles. |
showCollapseButtons | boolean | false | Shows controls for panels marked collapsible. |
keyboardResize | boolean | true | Enables arrow-key resizing. |
resetOnDoubleClick | boolean | true | Restores defaults on handle double-click. |
storageKey | string | — | Optional localStorage key for percentage sizes. |
variant | 'default' | 'card' | 'separated' | 'minimal' | 'default' | Visual treatment. |
className | string | — | Additional root CSS class. |
style | CSSProperties | — | Additional root inline styles. |
aria-label | string | 'Resizable panels' | Accessible name for the panel group. |
ResizablePanel properties
| Property | Type | Default | Description |
|---|---|---|---|
key | string | — | Unique panel identifier. |
content | ReactNode | — | Panel content. |
minSize | number | 5 | Minimum expanded size as a percentage. |
maxSize | number | 95 | Maximum expanded size as a percentage. |
defaultSize | number | Equal share | Initial panel size as a percentage. |
collapsible | boolean | false | Marks the panel as collapse-capable. |
collapsedSize | number | 0 | Percentage layout value while collapsed. |
collapsedMinSize | number | 34px | Physical minimum size while collapsed on the active axis. |
collapseIcon | ReactNode | Directional chevron | Icon shown while expanded, to collapse the panel. |
expandIcon | ReactNode | Directional chevron | Icon shown while collapsed, to expand the panel. |
ariaLabel | string | — | Accessible label for the panel region. |