Splitter

Resizable split pane layout

Usage

Splitter component provides resizable split pane layout. It is built on top of the use-splitter hook.

First pane
Second pane
Orientation
import { Splitter } from '@mantine/core';

function Demo() {
  return (
    <Splitter h={200}>
      <Splitter.Pane defaultSize={50} min={20} bg="blue">
        First pane
      </Splitter.Pane>
      <Splitter.Pane defaultSize={50} min={20} bg="teal">
        Second pane
      </Splitter.Pane>
    </Splitter>
  );
}

CSS units

Splitter.Pane defaultSize, min and max props accept CSS units in addition to plain numbers:

  • A plain number or a % string ('30%') is a flexible size – the pane shares the leftover space with other flexible panes.
  • A px or rem string ('240px', '15rem') is a fixed size – the pane keeps its pixel size when the container is resized and only changes when its own handle is dragged.

sizes and onSizeChange use the unit the pane was declared with: a '240px' pane stays '240px', a flexible pane reports a percentage. When the container is smaller than the sum of the fixed panes, fixed panes shrink proportionally.

Fixed 240px sidebar
Flexible content
import { Splitter } from '@mantine/core';

function Demo() {
  return (
    <Splitter h={200}>
      <Splitter.Pane defaultSize="240px" min="160px" max="50%" bg="blue">
        Fixed 240px sidebar
      </Splitter.Pane>
      <Splitter.Pane defaultSize={100} bg="teal">
        Flexible content
      </Splitter.Pane>
    </Splitter>
  );
}

Vertical orientation

Set orientation="vertical" to split panes vertically:

Top pane
Bottom pane
import { Splitter } from '@mantine/core';

function Demo() {
  return (
    <Splitter orientation="vertical" h={300}>
      <Splitter.Pane defaultSize={60} min={20} bg="blue">
        Top pane
      </Splitter.Pane>
      <Splitter.Pane defaultSize={40} min={20} bg="teal">
        Bottom pane
      </Splitter.Pane>
    </Splitter>
  );
}

Collapsible panes

Set collapsible prop on Splitter.Pane to allow the pane to be collapsed by dragging past its minimum size. Use splitterRef to access imperative API for programmatic collapse/expand:

Collapsible sidebar
Main content
import { useRef } from 'react';
import { Button, Group, Splitter } from '@mantine/core';
import { UseSplitterReturnValue } from '@mantine/hooks';

function Demo() {
  const splitterRef = useRef<UseSplitterReturnValue>(null);

  return (
    <>
      <Splitter splitterRef={splitterRef} h={200}>
        <Splitter.Pane defaultSize={30} min={20} collapsible bg="blue">
          Collapsible sidebar
        </Splitter.Pane>
        <Splitter.Pane defaultSize={70} min={30} bg="teal">
          Main content
        </Splitter.Pane>
      </Splitter>
      <Group mt="md">
        <Button size="xs" onClick={() => splitterRef.current?.toggleCollapse(0)}>
          Toggle sidebar
        </Button>
      </Group>
    </>
  );
}

Controlled

To control panel sizes, use sizes and onSizeChange props:

Panel A (50%)
Panel B (50%)

Current sizes: [50, 50]

import { useState } from 'react';
import { Button, Group, Splitter, Text } from '@mantine/core';

function Demo() {
  const [sizes, setSizes] = useState([50, 50]);

  return (
    <>
      <Splitter sizes={sizes} onSizeChange={setSizes} h={200}>
        <Splitter.Pane defaultSize={50} min={20} bg="blue">
          Panel A ({Math.round(sizes[0])}%)
        </Splitter.Pane>
        <Splitter.Pane defaultSize={50} min={20} bg="teal">
          Panel B ({Math.round(sizes[1])}%)
        </Splitter.Pane>
      </Splitter>
      <Text size="sm" mt="sm">
        Current sizes: [{sizes.map((s) => Math.round(s)).join(', ')}]
      </Text>
      <Group mt="xs">
        <Button size="xs" onClick={() => setSizes([30, 70])}>30 / 70</Button>
        <Button size="xs" onClick={() => setSizes([50, 50])}>50 / 50</Button>
        <Button size="xs" onClick={() => setSizes([70, 30])}>70 / 30</Button>
      </Group>
    </>
  );
}

Multiple panes

Splitter supports any number of panes. Handles are automatically rendered between panes:

First
Second
Third
import { Splitter } from '@mantine/core';

function Demo() {
  return (
    <Splitter h={200}>
      <Splitter.Pane defaultSize={25} min={10} bg="blue">
        First
      </Splitter.Pane>
      <Splitter.Pane defaultSize={50} min={10} bg="teal">
        Second
      </Splitter.Pane>
      <Splitter.Pane defaultSize={25} min={10} bg="grape">
        Third
      </Splitter.Pane>
    </Splitter>
  );
}

Nested splitters

You can nest Splitter components to create complex layouts:

Sidebar
Editor
Terminal
import { Splitter } from '@mantine/core';

function Demo() {
  return (
    <Splitter h={350}>
      <Splitter.Pane defaultSize={30} min={15} bg="blue">
        Sidebar
      </Splitter.Pane>
      <Splitter.Pane defaultSize={70} min={30}>
        <Splitter orientation="vertical" h="100%">
          <Splitter.Pane defaultSize={60} min={20} bg="teal">
            Editor
          </Splitter.Pane>
          <Splitter.Pane defaultSize={40} min={20} bg="grape">
            Terminal
          </Splitter.Pane>
        </Splitter>
      </Splitter.Pane>
    </Splitter>
  );
}

Redistribute

When using multiple panes, set redistribute prop to control how space is borrowed from non-adjacent panels when the immediate neighbor is at its min/max:

Panel A
Panel B
Panel C
Panel D
import { Splitter } from '@mantine/core';

function Demo() {
  return (
    <Splitter redistribute="nearest" h={200}>
      <Splitter.Pane defaultSize={25} min={10} bg="blue">
        Panel A
      </Splitter.Pane>
      <Splitter.Pane defaultSize={25} min={10} bg="teal">
        Panel B
      </Splitter.Pane>
      <Splitter.Pane defaultSize={25} min={10} bg="grape">
        Panel C
      </Splitter.Pane>
      <Splitter.Pane defaultSize={25} min={10} bg="orange">
        Panel D
      </Splitter.Pane>
    </Splitter>
  );
}

Line size

Use lineSize prop to control the thickness of the separator line between panes:

First pane
Second pane
Line size
import { Splitter } from '@mantine/core';

function Demo() {
  return (
    <Splitter h={200}>
      <Splitter.Pane defaultSize={50} min={20} bg="blue">
        First pane
      </Splitter.Pane>
      <Splitter.Pane defaultSize={50} min={20} bg="teal">
        Second pane
      </Splitter.Pane>
    </Splitter>
  );
}

Without handle

Set withHandle={false} to hide the thumb with grip icon. The separator line between panes is still visible and draggable:

First pane
Second pane
import { Splitter } from '@mantine/core';

function Demo() {
  return (
    <Splitter withHandle={false} h={200}>
      <Splitter.Pane defaultSize={50} min={20} bg="blue">
        First pane
      </Splitter.Pane>
      <Splitter.Pane defaultSize={50} min={20} bg="teal">
        Second pane
      </Splitter.Pane>
    </Splitter>
  );
}

Imperative API

Use splitterRef prop to access imperative splitter API:

import { useRef } from 'react';
import { Splitter } from '@mantine/core';
import { UseSplitterReturnValue } from '@mantine/hooks';

function Demo() {
  const splitterRef = useRef<UseSplitterReturnValue>(null);

  return (
    <>
      <button onClick={() => splitterRef.current?.collapse(0)}>
        Collapse first pane
      </button>
      <Splitter splitterRef={splitterRef}>
        <Splitter.Pane defaultSize={50} min={20} collapsible>
          First pane
        </Splitter.Pane>
        <Splitter.Pane defaultSize={50} min={20}>
          Second pane
        </Splitter.Pane>
      </Splitter>
    </>
  );
}

Wrap Splitter.Pane

Splitter component relies on Splitter.Pane order. Wrapping Splitter.Pane is not supported, Splitter.Pane must be a direct child of Splitter.

import { Splitter } from '@mantine/core';

// This will not work – wrapped pane will not be recognized
function WillNotWork() {
  return (
    <Splitter.Pane defaultSize={50} min={20}>
      This part will not render correctly
    </Splitter.Pane>
  );
}

// Create a separate component for pane content instead
function PaneContent() {
  return <div>This will work as expected!</div>;
}

function Demo() {
  return (
    <Splitter h={200}>
      <Splitter.Pane defaultSize={50} min={20}>
        First pane
      </Splitter.Pane>
      {/* Do not wrap Splitter.Pane in another component */}
      {/* <WillNotWork /> */}
      <Splitter.Pane defaultSize={50} min={20}>
        <PaneContent />
      </Splitter.Pane>
    </Splitter>
  );
}

use-splitter hook

Splitter component is built on top of the use-splitter hook. If you need more control over the layout and styling, you can use the hook directly.