Splitter

A number or percent size is flexible; a px or rem size stays fixed as the container changes.

Basics

Drag or focus the separator to resize the panes.

Loading demo…

import { Block, Splitter, Text } from '@plocks/ui'; export function Demo() { return ( <Block fullWidth> <Splitter h={200}> <Splitter.Pane defaultSize={35} min={20} bg="surface" p="md"> <Text>Sidebar</Text> </Splitter.Pane> <Splitter.Pane defaultSize={65} min={20} bg="subtle" p="md"> <Text>Content</Text> </Splitter.Pane> </Splitter> </Block> ); }

Fixed and Flexible Sizes

A pixel-sized sidebar keeps its width as the container changes.

Loading demo…

import { Block, Splitter, Text } from '@plocks/ui'; export function Demo() { return ( <Block fullWidth> <Splitter h={160}> <Splitter.Pane defaultSize="180px" min="120px" bg="subtle" p="md"> <Text>Fixed sidebar</Text> </Splitter.Pane> <Splitter.Pane defaultSize={100} bg="surface" p="md"> <Text>Flexible content</Text> </Splitter.Pane> </Splitter> </Block> ); }

Vertical Orientation

Vertical panes resize from a horizontal separator.

Loading demo…

import { Block, Splitter, Text } from '@plocks/ui'; export function Demo() { return ( <Block fullWidth> <Splitter orientation="vertical" h={220}> <Splitter.Pane defaultSize={50} bg="surface" p="md"> <Text>Top</Text> </Splitter.Pane> <Splitter.Pane defaultSize={50} bg="subtle" p="md"> <Text>Bottom</Text> </Splitter.Pane> </Splitter> </Block> ); }

Collapsible Pane

A collapsible pane can be toggled through the imperative ref.

Loading demo…

import { useRef } from 'react'; import { Block, Button, Splitter, Text } from '@plocks/ui'; import type { SplitterHandle } from '@plocks/ui'; export function Demo() { const splitterRef = useRef<SplitterHandle>(null); return ( <Block fullWidth> <Button size="sm" onPress={() => splitterRef.current?.toggleCollapse(0)}> Toggle sidebar </Button> <Splitter splitterRef={splitterRef} h={150} mt="sm"> <Splitter.Pane defaultSize={30} collapsible bg="subtle" p="md"> <Text>Sidebar</Text> </Splitter.Pane> <Splitter.Pane defaultSize={70} bg="surface" p="md"> <Text>Content</Text> </Splitter.Pane> </Splitter> </Block> ); }

Multiple Panes

Handles appear between every adjacent pair.

Loading demo…

import { Block, Splitter, Text } from '@plocks/ui'; export function Demo() { return ( <Block fullWidth> <Splitter h={160}> <Splitter.Pane defaultSize={25} bg="subtle" p="md"> <Text>One</Text> </Splitter.Pane> <Splitter.Pane defaultSize={50} bg="surface" p="md"> <Text>Two</Text> </Splitter.Pane> <Splitter.Pane defaultSize={25} bg="subtle" p="md"> <Text>Three</Text> </Splitter.Pane> </Splitter> </Block> ); }

Without Grip

withHandle={false} keeps the separator line without the visual grip.

Loading demo…

import { Block, Splitter, Text } from '@plocks/ui'; export function Demo() { return ( <Block fullWidth> <Splitter h={160} withHandle={false} lineSize={4}> <Splitter.Pane defaultSize={50} bg="surface" p="md"> <Text>One</Text> </Splitter.Pane> <Splitter.Pane defaultSize={50} bg="subtle" p="md"> <Text>Two</Text> </Splitter.Pane> </Splitter> </Block> ); }

Controlled Sizes

The parent owns the pane sizes through sizes and onSizeChange.

Loading demo…

import { useState } from 'react'; import { Block, Splitter, Text } from '@plocks/ui'; export function Demo() { const [sizes, setSizes] = useState<[number, number]>([40, 60]); return ( <Block fullWidth> <Splitter h={160} sizes={sizes} onSizeChange={(next) => setSizes(next as [number, number])}> <Splitter.Pane bg="subtle" p="md"> <Text>First ({Math.round(sizes[0])}%)</Text> </Splitter.Pane> <Splitter.Pane bg="surface" p="md"> <Text>Second</Text> </Splitter.Pane> </Splitter> </Block> ); }

Nested Splitters

A pane can contain another splitter with its own direction.

Loading demo…

import { Block, Splitter, Text } from '@plocks/ui'; export function Demo() { return ( <Block fullWidth> <Splitter h={220}> <Splitter.Pane defaultSize={40} bg="subtle" p="md"> <Text>Left</Text> </Splitter.Pane> <Splitter.Pane defaultSize={60}> <Splitter orientation="vertical" h={220}> <Splitter.Pane defaultSize={50} bg="surface" p="md"> <Text>Top</Text> </Splitter.Pane> <Splitter.Pane defaultSize={50} bg="subtle" p="md"> <Text>Bottom</Text> </Splitter.Pane> </Splitter> </Splitter.Pane> </Splitter> </Block> ); }

Redistribute Space

redistribute="nearest" borrows from farther panes when a neighbor reaches its minimum.

Loading demo…

import { Block, Splitter, Text } from '@plocks/ui'; export function Demo() { return ( <Block fullWidth> <Splitter h={160} redistribute="nearest"> {['A', 'B', 'C', 'D'].map((item) => ( <Splitter.Pane key={item} defaultSize={25} min={item === 'B' ? 20 : 10} bg="subtle" p="sm" > <Text>{item}</Text> </Splitter.Pane> ))} </Splitter> </Block> ); }

Line Size

lineSize and handleColor customize the visible separator.

Loading demo…

import { Block, Splitter, Text, useTheme } from '@plocks/ui'; export function Demo() { const theme = useTheme(); return ( <Block fullWidth> <Splitter h={160} lineSize={4} handleColor={theme.colors.primary[5]}> <Splitter.Pane defaultSize={40} bg="subtle" p="md"> <Text>Navigation</Text> </Splitter.Pane> <Splitter.Pane defaultSize={60} bg="surface" p="md"> <Text>Content</Text> </Splitter.Pane> </Splitter> </Block> ); }

Imperative API

splitterRef exposes collapse and expand controls for each pane.

Loading demo…

import { useRef } from 'react'; import { Block, Button, Flex, Splitter, Text } from '@plocks/ui'; import type { SplitterHandle } from '@plocks/ui'; export function Demo() { const splitterRef = useRef<SplitterHandle>(null); return ( <Block fullWidth> <Flex gap="sm"> <Button onPress={() => splitterRef.current?.collapse(0)}>Collapse</Button> <Button onPress={() => splitterRef.current?.expand(0)}>Expand</Button> </Flex> <Splitter h={160} splitterRef={splitterRef} mt="sm"> <Splitter.Pane defaultSize={35} collapsible bg="subtle" p="md"> <Text>Sidebar</Text> </Splitter.Pane> <Splitter.Pane defaultSize={65} bg="surface" p="md"> <Text>Content</Text> </Splitter.Pane> </Splitter> </Block> ); }
Scan to open this page on your phoneplocks.dev/ui/Splitter