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
Left Panel
Drag the gutter to resize horizontally.
Right Panel
Fills the remaining space automatically.
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
Top Panel
Drag the horizontal gutter to resize.
Bottom Panel
Fills the remaining vertical space.
<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
Sidebar
Starts at 280 px wide.
Content Area
Expands to fill the remaining width.
<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
30% Panel
Starts at 30% of container width.
70% Panel
Fills the remaining 70%.
<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%'
Min 150px
Cannot collapse below 150 px.
Min 20%
Cannot collapse below 20% of container width.
<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
Fixed (200px)
This panel is locked and cannot be resized.
Fluid Panel
Fills all remaining space.
<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
Left Panel
Adjust the slider above to change gutter thickness.
Right Panel
<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>Responsive Collapse
Set responsive to a pixel width on a horizontal splitter. When the splitter's own container shrinks below that width, the side-by-side panels collapse into a vertical stack. Only the layout styles flip and the draggable gutter becomes a static divider — the panels themselves are never unmounted, so any state inside them (form values, scroll position, counters) is preserved. This applies to layout="horizontal" only; a vertical splitter is already stacked and ignores the prop.
Responsive collapse — horizontal turns into a vertical stack
Container: 760px · Threshold: 520px · Layout: vertical (collapsed)
const [collapsed, setCollapsed] = useState(false);
<Splitter
style={{ height: '100%' }}
responsive={520}
onCollapseChange={setCollapsed}
>
<SplitterPanel defaultSize="40%" minSize="160px">
<div className="p-4">Filters</div>
</SplitterPanel>
<SplitterPanel>
<div className="p-4">Results</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
Drag me
Release the gutter to fire the callback.
Right Panel
Drag and release to see the callback value.
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
Sidebar
File explorer
Editor
Main editor area
Terminal
Output panel
<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.
responsivenumberContainer width in px (measured on the splitter itself, not the viewport) below which a horizontal splitter collapses from side-by-side panels into a stacked layout. Applies to layout="horizontal" only — a vertical splitter is already stacked and ignores it. The collapse only flips layout styles and swaps the draggable gutter for a static divider, so panels are never unmounted and child state is preserved. Omit to disable.
responsivenumberContainer width in px (measured on the splitter itself, not the viewport) below which a horizontal splitter collapses from side-by-side panels into a stacked layout. Applies to layout="horizontal" only — a vertical splitter is already stacked and ignores it. The collapse only flips layout styles and swaps the draggable gutter for a static divider, so panels are never unmounted and child state is preserved. Omit to disable.
collapsedLayout'horizontal' | 'vertical'"'vertical'"Direction panels stack in once collapsed (only used with responsive).
collapsedLayout'horizontal' | 'vertical'"'vertical'"Direction panels stack in once collapsed (only used with responsive).
onCollapseChange(collapsed: boolean) => voidCalled when the splitter crosses the responsive threshold, with the new collapsed state.
onCollapseChange(collapsed: boolean) => voidCalled when the splitter crosses the responsive threshold, with the new collapsed state.
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