Fluxo UIFluxo UIv0.1.1

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>

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.

KeyAction
← / →Move gutter left / right by 20px (horizontal layout)
↑ / ↓Move gutter up / down by 20px (vertical layout)
TabFocus 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.

gutterSize
number"4"

Width (or height) in pixels of the drag handle bar between panels.

onResizeEnd
(firstPanelSize: number) => void

Called after the user finishes a drag with the new first-panel size in pixels.

className
string

Additional CSS classes for the outer container.

style
React.CSSProperties

Inline styles for the outer container.

childrenreq
React.ReactNode

Exactly two SplitterPanel children are required.

SplitterPanel Props

defaultSize
string

Initial 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.

minSize
string"'0px'"

Minimum size of the panel. Accepts px or %. Prevents the panel from being resized below this threshold.

fixed
boolean"false"

When true, the panel cannot be resized by the user.

className
string

Additional CSS classes for the panel container.

style
React.CSSProperties

Inline styles for the panel container.

children
React.ReactNode

Content 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