import { Center, Splitter } from '@chakra-ui/react'
export const SplitterBasic = () => {
return (
<Splitter.Root
panels={[{ id: 'a' }, { id: 'b' }]}
borderWidth="1px"
minH="60"
>
<Splitter.Panel id="a">
<Center boxSize="full" textStyle="2xl">
A
</Center>
</Splitter.Panel>
<Splitter.ResizeTrigger id="a:b" />
<Splitter.Panel id="b">
<Center boxSize="full" textStyle="2xl">
B
</Center>
</Splitter.Panel>
</Splitter.Root>
)
}
Anatomy
import { Splitter } from '@chakra-ui/react'<Splitter.Root>
<Splitter.Panel />
<Splitter.ResizeTrigger>
<Splitter.ResizeTriggerSeparator />
<Splitter.ResizeTriggerIndicator />
</Splitter.ResizeTrigger>
<Splitter.Panel />
</Splitter.Root>The Splitter.ResizeTrigger renders the Splitter.ResizeTriggerSeparator and
Splitter.ResizeTriggerIndicator for you, so writing
<Splitter.ResizeTrigger id="a:b" /> is enough unless you need to customize the
separator or indicator.
Examples
Controlled
Use the size and onResize props to manage panel sizes programmatically.
<Splitter.Root
panels={[{ id: 'a' }, { id: 'b' }]}
size={sizes}
onResize={(details) => setSizes(details.size)}
>
{/* ... */}
</Splitter.Root>Panel A: 50.0% | Panel B: 50.0%'use client'
import { useState } from 'react'
import { Center, Code, HStack, Span, Splitter, Stack } from '@chakra-ui/react'
import { LuMouse, LuMoveHorizontal } from 'react-icons/lu'
export const SplitterControlled = () => {
const [sizes, setSizes] = useState([50, 50])
return (
<Stack gap="4" align="start">
<HStack textStyle="sm" gap={2}>
<LuMouse />
<LuMoveHorizontal />
<Span>Drag the handle to resize panels</Span>
</HStack>
<Splitter.Root
panels={[{ id: 'a' }, { id: 'b' }]}
size={sizes}
onResize={(details) => setSizes(details.size)}
borderWidth="1px"
minH="60"
>
<Splitter.Panel id="a">
<Center boxSize="full" textStyle="2xl">
A
</Center>
</Splitter.Panel>
<Splitter.ResizeTrigger id="a:b" />
<Splitter.Panel id="b">
<Center boxSize="full" textStyle="2xl">
B
</Center>
</Splitter.Panel>
</Splitter.Root>
<Code color="fg.muted">
Panel A: {sizes[0].toFixed(1)}% | Panel B: {sizes[1].toFixed(1)}%
</Code>
</Stack>
)
}
Vertical
Pass the orientation="vertical" prop to the Splitter.Root component for
stacked panels that resize vertically.
import { Center, Splitter } from '@chakra-ui/react'
export const SplitterVertical = () => {
return (
<Splitter.Root
panels={[{ id: 'a' }, { id: 'b' }]}
orientation="vertical"
borderWidth="1px"
minH="60"
>
<Splitter.Panel id="a">
<Center boxSize="full" textStyle="2xl">
A
</Center>
</Splitter.Panel>
<Splitter.ResizeTrigger id="a:b" />
<Splitter.Panel id="b">
<Center boxSize="full" textStyle="2xl">
B
</Center>
</Splitter.Panel>
</Splitter.Root>
)
}
Multiple Panels
Create layouts with more than two resizable panels by passing an array of panels
to the panels prop of the Splitter.Root component.
import { Center, Splitter } from '@chakra-ui/react'
export const SplitterMultiplePanels = () => {
return (
<Splitter.Root
panels={[{ id: 'a' }, { id: 'b' }, { id: 'c' }]}
borderWidth="1px"
minH="60"
>
<Splitter.Panel id="a">
<Center boxSize="full" textStyle="2xl">
A
</Center>
</Splitter.Panel>
<Splitter.ResizeTrigger id="a:b" aria-label="Resize" />
<Splitter.Panel id="b">
<Center boxSize="full" textStyle="2xl">
B
</Center>
</Splitter.Panel>
<Splitter.ResizeTrigger id="b:c" aria-label="Resize" />
<Splitter.Panel id="c">
<Center boxSize="full" textStyle="2xl">
C
</Center>
</Splitter.Panel>
</Splitter.Root>
)
}
Collapsible Panels
Make the panels collapsible and snapped to a specific size by setting the
collapsible and collapsedSize properties on a panel in the panels array.
<Splitter.Root
defaultSize={[40, 60]}
panels={[
{ id: 'a', collapsible: true, collapsedSize: 5, minSize: 25 },
{ id: 'b', minSize: 50 },
]}
>
{/* ... */}
</Splitter.Root>import { Box, Center, HStack, Splitter } from '@chakra-ui/react'
import { LuMouse, LuMoveHorizontal } from 'react-icons/lu'
export const SplitterCollapsible = () => {
return (
<Box>
<HStack textStyle="sm" mb={4}>
<LuMouse />
<LuMoveHorizontal />
Drag the resizer to collapse or expand Panel A
</HStack>
<Splitter.Root
defaultSize={[40, 60]}
panels={[
{ id: 'a', collapsible: true, collapsedSize: 5, minSize: 25 },
{ id: 'b', minSize: 50 },
]}
borderWidth="1px"
minH="60"
>
<Splitter.Panel id="a">
<Center boxSize="full" textStyle="2xl">
A
</Center>
</Splitter.Panel>
<Splitter.ResizeTrigger id="a:b" />
<Splitter.Panel id="b">
<Center boxSize="full" textStyle="2xl">
B
</Center>
</Splitter.Panel>
</Splitter.Root>
</Box>
)
}
Min/Max Constraints
Set minSize and maxSize on panels to constrain their resizable range and
prevent resizing beyond these boundaries.
30.0% (min: 20%, max: 60%)70.0% (min: 40%)'use client'
import { useState } from 'react'
import { Center, Code, HStack, Span, Splitter, Stack } from '@chakra-ui/react'
import { LuMouse, LuMoveHorizontal } from 'react-icons/lu'
export const SplitterMinMaxConstraints = () => {
const [sizes, setSizes] = useState([30, 70])
return (
<Stack gap="4" align="start">
<HStack textStyle="sm" gap={2}>
<LuMouse />
<LuMoveHorizontal />
<Span>Drag to resize - Panel A: 20-60%, Panel B: min 40%</Span>
</HStack>
<Splitter.Root
panels={[
{ id: 'a', minSize: 20, maxSize: 60 },
{ id: 'b', minSize: 40 },
]}
defaultSize={[30, 70]}
borderWidth="1px"
size={sizes}
onResize={(details) => setSizes(details.size)}
minH="60"
>
<Splitter.Panel id="a">
<Stack boxSize="full" align="center" justify="center" gap="2">
<Center textStyle="2xl">A</Center>
<Code size="sm" color="fg.muted">
{sizes[0].toFixed(1)}% (min: 20%, max: 60%)
</Code>
</Stack>
</Splitter.Panel>
<Splitter.ResizeTrigger id="a:b" />
<Splitter.Panel id="b">
<Stack boxSize="full" align="center" justify="center" gap="2">
<Center textStyle="2xl">B</Center>
<Code size="sm" color="fg.muted">
{sizes[1].toFixed(1)}% (min: 40%)
</Code>
</Stack>
</Splitter.Panel>
</Splitter.Root>
</Stack>
)
}
Nested Panels
Nest splitters inside panels to create more complex layouts. Each nested splitter can have its own orientation, sizes, and behaviors independent of the parent splitter.
import { Center, Splitter } from '@chakra-ui/react'
export const SplitterNested = () => {
return (
<Splitter.Root panels={[{ id: 'a' }, { id: 'b' }]} borderWidth="1px">
<Splitter.Panel id="a">
<Center boxSize="full" textStyle="2xl">
A
</Center>
</Splitter.Panel>
<Splitter.ResizeTrigger id="a:b" aria-label="Resize" />
<Splitter.Panel id="b">
<Splitter.Root
panels={[{ id: 'b1' }, { id: 'b2' }]}
orientation="vertical"
minH="80"
>
<Splitter.Panel id="b1">
<Center boxSize="full" textStyle="2xl">
B1
</Center>
</Splitter.Panel>
<Splitter.ResizeTrigger
id="b1:b2"
aria-label="Resize nested panels"
/>
<Splitter.Panel id="b2">
<Center boxSize="full" textStyle="2xl">
B2
</Center>
</Splitter.Panel>
</Splitter.Root>
</Splitter.Panel>
</Splitter.Root>
)
}
Storage
Set a defaultSize and pair it with a storage solution, such as
useLocalStorage, to save users' panel size preferences. This ensures that
panel layouts persist across sessions. Alternatively, you can use cookies or
other storage mechanisms depending on your needs.
'use client'
import {
Badge,
Button,
Center,
HStack,
Span,
Splitter,
Stack,
} from '@chakra-ui/react'
import { LuBox, LuMouse, LuMoveHorizontal, LuTrash2 } from 'react-icons/lu'
import { useLocalStorage } from 'react-use'
export const SplitterWithStorage = () => {
const [sizes, setSizes] = useLocalStorage('splitter-sizes', [70, 50])
const formattedSizes = sizes?.map((size) => size.toFixed(1)).join(', ')
const hasSavedState = sizes && sizes.length > 0
const clearStorage = () => {
setSizes(undefined)
}
return (
<Stack gap="4" align="start">
<HStack textStyle="sm" alignSelf="stretch" justify="space-between">
<HStack>
<LuMouse />
<LuMoveHorizontal />
<Span>Drag to resize panels</Span>
</HStack>
{hasSavedState && (
<Button size="xs" variant="ghost" onClick={clearStorage}>
<LuTrash2 /> Clear Storage
</Button>
)}
</HStack>
<Splitter.Root
panels={[{ id: 'a' }, { id: 'b' }]}
defaultSize={sizes}
onResizeEnd={(e) => setSizes(e.size)}
borderWidth="1px"
minH="60"
>
<Splitter.Panel id="a">
<Center boxSize="full" textStyle="2xl">
A
</Center>
</Splitter.Panel>
<Splitter.ResizeTrigger id="a:b" />
<Splitter.Panel id="b">
<Center boxSize="full" textStyle="2xl">
B
</Center>
</Splitter.Panel>
</Splitter.Root>
<Badge>
<LuBox /> LocalStorage{' '}
{hasSavedState ? `[${formattedSizes}]` : '[Not saved]'}
</Badge>
</Stack>
)
}
Props
Root
| Prop | Default | Type |
|---|---|---|
panels * | PanelData[]The size constraints of the panels. | |
orientation | '\'horizontal\'' | 'horizontal' | 'vertical'The orientation of the splitter. Can be `horizontal` or `vertical` |
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
defaultSize | PanelSize[]The initial size of the panels when rendered. Use when you don't need to control the size of the panels. | |
id | stringThe unique identifier of the machine. | |
ids | Partial<{
root: string
resizeTrigger: (id: string) => string
label: (id: string) => string
panel: (id: string | number) => string
}>The ids of the elements in the splitter. Useful for composition. | |
keyboardResizeBy | numberThe number of pixels to resize the panel by when the keyboard is used. | |
nonce | stringThe nonce for the injected splitter cursor stylesheet. | |
onCollapse | (details: ExpandCollapseDetails) => voidFunction called when a panel is collapsed. | |
onExpand | (details: ExpandCollapseDetails) => voidFunction called when a panel is expanded. | |
onResize | (details: ResizeDetails) => voidFunction called when the splitter is resized. | |
onResizeEnd | (details: ResizeEndDetails) => voidFunction called when the splitter resize ends. | |
onResizeStart | () => voidFunction called when the splitter resize starts. | |
registry | SplitterRegistryThe splitter registry to use for multi-drag support. When provided, enables dragging at the intersection of multiple splitters. | |
size | PanelSize[]The controlled size data of the panels |
Panel
| Prop | Default | Type |
|---|---|---|
id * | string | |
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
ResizeTrigger
| Prop | Default | Type |
|---|---|---|
id * | ResizeTriggerId | |
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
disabled | boolean |