Skip to content

Interactive Controller Demo ​

The Split component allows for imperative control via a React ref. This is the recommended way to trigger layout changes from outside the component, such as header buttons or sidebar toggles.

Using the Ref API ​

To control the splitter programmatically, capture the SplitRef and call its built-in methods.

tsx
import { useRef } from 'react';
import { Split, type SplitRef } from '@a-multilayout-splitter/core';

function MyView() {
  const splitRef = useRef<SplitRef>(null);

  return (
    <>
      <button onClick={() => splitRef.current?.togglePane(0)}>
        Toggle Sidebar
      </button>

      <Split ref={splitRef}>
        <div>Sidebar</div>
        <div>Main Content</div>
      </Split>
    </>
  );
}

Why use the Ref API? ​

  • Stability: Methods on the ref have stable identities and don't trigger parent re-renders.
  • Performance: Operations like collapsePane or addPane happen instantly within the component.
  • Simplicity: No need to manage complex pane state arrays in your parent component.

Using the useSplitController Hook ​

For scenarios where you need to manage pane state externally (e.g., sync with Redux/Zustand, build custom toolbars), use the useSplitController hook. The controller state can drive the collapsed prop on Split for reactive updates with full drag functionality.

tsx
import { useSplitController, Split } from '@a-multilayout-splitter/core';

function ControlledSplitLayout() {
  const controller = useSplitController({
    mode: 'horizontal',
    initialPanes: [
      { id: 'sidebar', size: '25%', collapsed: false, minSize: 10, maxSize: 50, content: null },
      { id: 'main', size: '50%', collapsed: false, minSize: 20, maxSize: 80, content: null },
      { id: 'panel', size: '25%', collapsed: false, minSize: 10, maxSize: 50, content: null },
    ],
    onPaneChange: (panes) => {
      // Sync with your state management (Redux, Zustand, etc.)
      console.log('Panes updated:', panes);
    },
  });

  // Derive collapsed array from controller to sync with Split
  const collapsedState = controller.panes.map(p => p.collapsed);

  return (
    <div>
      {/* External Control Panel */}
      <header>
        <button onClick={() => controller.togglePane(0)}>
          {controller.panes[0]?.collapsed ? 'Show' : 'Hide'} Sidebar
        </button>
        <button onClick={() => controller.togglePane(2)}>
          {controller.panes[2]?.collapsed ? 'Show' : 'Hide'} Panel
        </button>
        <span>Collapsed: {controller.panes.filter(p => p.collapsed).length}</span>
      </header>

      {/* Split with draggable handles, controlled by hook state */}
      <Split
        initialSizes={['25%', '50%', '25%']}
        collapsed={collapsedState}
        minSizes={[10, 20, 10]}
        maxSizes={[50, 80, 50]}
      >
        <div>Sidebar</div>
        <div>Main Content</div>
        <div>Right Panel</div>
      </Split>
    </div>
  );
}

Why use the Hook API? ​

  • Sync with UI: Update buttons or nav items based on whether a pane is open.
  • State Management: Integrate with Redux, Zustand, or any external store via onPaneChange.
  • State Serialization: Save the entire panes array for persistence or undo/redo functionality.
  • Reactive UI: Build dynamic toolbars that reflect current pane state in real-time.

Available Controller Methods ​

MethodDescription
addPane(config)Add a new pane at a specific position
removePane(index)Remove a pane and redistribute its size
removePanes(indices)Remove multiple panes at once
togglePane(index)Toggle a pane's collapsed state
collapsePane(index)Force collapse a pane
expandPane(index)Force expand a pane
setPaneSize(index, size)Set a specific pane's size
swapPanes(indexA, indexB)Swap two panes' positions
getSnapshot()Get a serializable snapshot of the current state
restore(snapshot)Restore state from a snapshot

Controller State ​

The hook also returns reactive state:

tsx
const { panes, mode, isDragging } = useSplitController({ ... });

// panes: Pane[] - current pane configurations
// mode: 'horizontal' | 'vertical' - the layout direction
// isDragging: boolean - whether user is currently resizing

Ref API vs Hook API ​

FeatureRef API (SplitRef)Hook API (useSplitController)
State ownershipInternal to SplitExternal, in your component
Reading pane stateVia callbacksDirectly from controller.panes
Triggering actionsref.current?.method()controller.method()
Best forSimple toggling, quick actionsComplex state sync, custom UIs
Re-rendersMinimal (ref is stable)When panes change

Nested Layouts ​

For controlling nested splits (IDE-like layouts), see the dedicated Nested Control guide.

Released under the MIT License.