Splitter
A resizable split-panel component that divides a container into two adjustable sections. Supports horizontal and vertical layouts with drag, touch, and keyboard resize support.
Basic Horizontal Split
Default layout — panels sit side by side. Drag the gutter to resize. The second panel fills all remaining space.
Horizontal Split
import { Splitter, SplitterPanel } from 'fluxo-ui';
<Splitter style={{ height: '200px' }}>
<SplitterPanel>
<div className="p-4">Left Panel</div>
</SplitterPanel>
<SplitterPanel>
<div className="p-4">Right Panel</div>
</SplitterPanel>
</Splitter>Vertical Split
Use layout="vertical" to stack panels top-to-bottom with a horizontal drag handle.
Vertical Split
<Splitter layout="vertical" style={{ height: '300px' }}>
<SplitterPanel>
<div className="p-4">Top Panel</div>
</SplitterPanel>
<SplitterPanel>
<div className="p-4">Bottom Panel</div>
</SplitterPanel>
</Splitter>Default Size (px)
Set defaultSize on one panel to give it a fixed starting size. The sibling fills the rest. Only set this on one panel per splitter.
defaultSize='280px' on first panel
<Splitter style={{ height: '200px' }}>
<SplitterPanel defaultSize="280px">
<div className="p-4">Sidebar — starts at 280px</div>
</SplitterPanel>
<SplitterPanel>
<div className="p-4">Content Area</div>
</SplitterPanel>
</Splitter>Default Size (%)
Percentages are also supported, making the initial split responsive to the container width.
defaultSize='30%' on first panel
<Splitter style={{ height: '200px' }}>
<SplitterPanel defaultSize="30%">
<div className="p-4">30% Panel</div>
</SplitterPanel>
<SplitterPanel>
<div className="p-4">70% Panel</div>
</SplitterPanel>
</Splitter>Minimum Panel Size
Use minSize to prevent a panel from being resized below a threshold. Accepts px or %. Try dragging the gutter all the way to either edge.
minSize='150px' and minSize='20%'
<Splitter style={{ height: '200px' }}>
<SplitterPanel minSize="150px">
<div className="p-4">Min 150px</div>
</SplitterPanel>
<SplitterPanel minSize="20%">
<div className="p-4">Min 20%</div>
</SplitterPanel>
</Splitter>Fixed Panel
Set fixed on a panel to lock its size. The gutter is rendered but dragging will not change the fixed panel.
fixed panel — cannot be resized
<Splitter style={{ height: '200px' }}>
<SplitterPanel defaultSize="200px" fixed>
<div className="p-4">Fixed (200px) — cannot be resized</div>
</SplitterPanel>
<SplitterPanel>
<div className="p-4">Fluid Panel</div>
</SplitterPanel>
</Splitter>Custom Gutter Size
Control how thick the drag handle bar is with gutterSize (in pixels). Larger gutters are easier to grab on touch devices.
gutterSize — drag the slider to change gutter thickness
<Splitter gutterSize={10} style={{ height: '200px' }}>
<SplitterPanel>
<div className="p-4">Left Panel</div>
</SplitterPanel>
<SplitterPanel>
<div className="p-4">Right Panel</div>
</SplitterPanel>
</Splitter>onResizeEnd Callback
onResizeEnd fires after the user releases the gutter, providing the first panel's pixel size. Use it to persist the layout to storage.
onResizeEnd — fires when drag ends
const [savedSize, setSavedSize] = useState<number | null>(null);
<Splitter
style={{ height: '200px' }}
onResizeEnd={(size) => {
setSavedSize(size);
localStorage.setItem('splitter-size', String(size));
}}
>
<SplitterPanel>
<div className="p-4">Panel A</div>
</SplitterPanel>
<SplitterPanel>
<div className="p-4">Panel B</div>
</SplitterPanel>
</Splitter>Nested Splitters
Place a Splitter inside a SplitterPanel to build IDE-style multi-pane layouts. Make sure the inner splitter fills its panel with height: 100%.
IDE-style layout — sidebar + editor + terminal
<Splitter style={{ height: '300px' }}>
<SplitterPanel defaultSize="200px">
<div className="p-4">Sidebar</div>
</SplitterPanel>
<SplitterPanel>
<Splitter layout="vertical" style={{ height: '100%' }}>
<SplitterPanel>
<div className="p-4">Editor</div>
</SplitterPanel>
<SplitterPanel defaultSize="100px">
<div className="p-4">Terminal</div>
</SplitterPanel>
</Splitter>
</SplitterPanel>
</Splitter>Persisting Layout
Combine defaultSize and onResizeEnd to save and restore the user's preferred split across sessions.
function PersistentSplitter() {
const storageKey = 'my-splitter-size';
const savedSize = (() => {
const v = localStorage.getItem(storageKey);
return v ? `${v}px` : '300px';
})();
return (
<Splitter
style={{ height: '400px' }}
onResizeEnd={(size) => {
localStorage.setItem(storageKey, String(Math.round(size)));
}}
>
<SplitterPanel defaultSize={savedSize} minSize="100px">
<div className="p-4">Sidebar — size restored on reload</div>
</SplitterPanel>
<SplitterPanel minSize="200px">
<div className="p-4">Main content</div>
</SplitterPanel>
</Splitter>
);
}Keyboard Navigation
The gutter is focusable and responds to arrow keys, making it fully ADA-accessible without any extra configuration.
| Key | Action |
|---|---|
| ← / → | Move gutter left / right by 20px (horizontal layout) |
| ↑ / ↓ | Move gutter up / down by 20px (vertical layout) |
| Tab | Focus the gutter from the page tab order |
Import
import { Splitter, SplitterPanel } from 'fluxo-ui';Splitter Props
layout'horizontal' | 'vertical'"'horizontal'"Direction in which panels are laid out. 'horizontal' places panels side by side; 'vertical' stacks them.
layout'horizontal' | 'vertical'"'horizontal'"Direction in which panels are laid out. 'horizontal' places panels side by side; 'vertical' stacks them.
gutterSizenumber"4"Width (or height) in pixels of the drag handle bar between panels.
gutterSizenumber"4"Width (or height) in pixels of the drag handle bar between panels.
onResizeEnd(firstPanelSize: number) => voidCalled after the user finishes a drag with the new first-panel size in pixels.
onResizeEnd(firstPanelSize: number) => voidCalled after the user finishes a drag with the new first-panel size in pixels.
classNamestringAdditional CSS classes for the outer container.
classNamestringAdditional CSS classes for the outer container.
styleReact.CSSPropertiesInline styles for the outer container.
styleReact.CSSPropertiesInline styles for the outer container.
childrenreqReact.ReactNodeExactly two SplitterPanel children are required.
childrenreqReact.ReactNodeExactly two SplitterPanel children are required.
SplitterPanel Props
defaultSizestringInitial size of the panel. Accepts px (e.g. '300px') or % (e.g. '40%'). Only one panel should set this — the sibling fills the remaining space.
defaultSizestringInitial size of the panel. Accepts px (e.g. '300px') or % (e.g. '40%'). Only one panel should set this — the sibling fills the remaining space.
minSizestring"'0px'"Minimum size of the panel. Accepts px or %. Prevents the panel from being resized below this threshold.
minSizestring"'0px'"Minimum size of the panel. Accepts px or %. Prevents the panel from being resized below this threshold.
fixedboolean"false"When true, the panel cannot be resized by the user.
fixedboolean"false"When true, the panel cannot be resized by the user.
classNamestringAdditional CSS classes for the panel container.
classNamestringAdditional CSS classes for the panel container.
styleReact.CSSPropertiesInline styles for the panel container.
styleReact.CSSPropertiesInline styles for the panel container.
childrenReact.ReactNodeContent to render inside the panel.
childrenReact.ReactNodeContent to render inside the panel.
Features
Horizontal & Vertical
Side-by-side or stacked layouts via the layout prop
px & % Sizing
defaultSize and minSize accept both pixel and percentage values
Minimum Size
minSize prevents panels from collapsing below a defined threshold
Fixed Panels
Lock a panel's size to prevent user resizing with the fixed prop
Drag & Touch
Smooth resizing via mouse drag and touch events on all devices
Nested Splitters
Compose multiple splitters for IDE-style multi-pane layouts
onResizeEnd Callback
Fires with the first panel's pixel size after drag ends — perfect for persistence
Keyboard Navigation
Arrow keys move the gutter by 20px; fully ADA accessible without extra config
Theming
Full dark/light and 5 brand themes via CSS variables — zero extra config