Splitter
A splitter creates resizable layouts with horizontal or vertical panels.
Features
- Built with flexbox for flexible layout and SSR
- Supports horizontal and vertical panels
- Supports multiple panels and resize handles
- Supports collapsible panels
- Supports panel constraints like min and max sizes
- Programmatic control of panel sizes
- Implements the Window Splitter pattern for accessibility and keyboard controls
Installation
Install the splitter package:
npm install @zag-js/splitter @zag-js/react # or yarn add @zag-js/splitter @zag-js/react
npm install @zag-js/splitter @zag-js/solid # or yarn add @zag-js/splitter @zag-js/solid
npm install @zag-js/splitter @zag-js/vue # or yarn add @zag-js/splitter @zag-js/vue
npm install @zag-js/splitter @zag-js/svelte # or yarn add @zag-js/splitter @zag-js/svelte
Anatomy
Check the splitter anatomy and part names.
Each part includes a
data-partattribute to help identify them in the DOM.
Usage
Import the splitter package:
import * as splitter from "@zag-js/splitter"
The splitter package exports two key functions:
machine- State machine logic.connect- Maps machine state to JSX props and event handlers.
Then use the framework integration helpers:
import * as splitter from "@zag-js/splitter" import { useMachine, normalizeProps } from "@zag-js/react" import { useId } from "react" export function Splitter() { const service = useMachine(splitter.machine, { id: useId(), defaultSize: [80, 20], panels: [ { id: "a", minSize: 10 }, { id: "b", minSize: 10 }, ], }) const api = slider.connect(service, normalizeProps) return ( <div {...api.getRootProps()}> <div {...api.getPanelProps({ id: "a" })}> <p>A</p> </div> <div {...api.getResizeTriggerProps({ id: "a:b" })} /> <div {...api.getPanelProps({ id: "b" })}> <p>B</p> </div> </div> ) }
import * as splitter from "@zag-js/splitter" import { normalizeProps, useMachine } from "@zag-js/solid" import { createMemo, createUniqueId } from "solid-js" export function Splitter() { const service = useMachine(splitter.machine, { id: createUniqueId(), defaultSize: [80, 20], panels: [ { id: "a", minSize: 10 }, { id: "b", minSize: 10 }, ], }) const api = createMemo(() => splitter.connect(service, normalizeProps)) return ( <div {...api().getRootProps()}> <div {...api().getPanelProps({ id: "a" })}> <p>A</p> </div> <div {...api().getResizeTriggerProps({ id: "a:b" })} /> <div {...api().getPanelProps({ id: "b" })}> <p>B</p> </div> </div> ) }
<script setup> import * as splitter from "@zag-js/splitter" import { normalizeProps, useMachine } from "@zag-js/vue" import { computed } from "vue" const service = useMachine(splitter.machine, { id: "1", defaultSize: [80, 20], panels: [ { id: "a", minSize: 10 }, { id: "b", minSize: 10 }, ], }) const api = computed(() => splitter.connect(service, normalizeProps)) </script> <template> <div v-bind="api.getRootProps()"> <div v-bind="api.getPanelProps({ id: 'a' })"> <p>A</p> </div> <div v-bind="api.getResizeTriggerProps({ id: 'a:b' })" /> <div v-bind="api.getPanelProps({ id: 'b' })"> <p>B</p> </div> </div> </template>
<script lang="ts"> import { normalizeProps, useMachine } from "@zag-js/svelte" import * as splitter from "@zag-js/splitter" const id = $props.id() const service = useMachine(splitter.machine, { id, defaultSize: [80, 20], panels: [ { id: "a", minSize: 10 }, { id: "b", minSize: 10 }, ], }) const api = $derived(splitter.connect(service, normalizeProps)) </script> <div {...api.getRootProps()}> <div {...api.getPanelProps({ id: "a" })}> <p>A</p> </div> <div {...api.getResizeTriggerProps({ id: "a:b" })}></div> <div {...api.getPanelProps({ id: "b" })}> <p>B</p> </div> </div>
Setting the initial size
To set the initial size of the splitter panels, use the defaultSize property.
Ensure the defaultSize totals to 100.
Note: The splitter only supports setting percentage values.
const service = useMachine(splitter.machine, { // ... defaultSize: [40, 60], })
Controlled panel sizes
Use size and onResize for controlled layouts.
const service = useMachine(splitter.machine, { panels, size, onResize(details) { setSize(details.size) }, })
Listening for resize events
When the resize trigger is dragged, the onResize, onResizeStart and
onResizeEnd callbacks are invoked.
const service = useMachine(splitter.machine, { // ... onResize(detail) { console.log("resize", detail) }, onResizeStart() { console.log("resize start") }, onResizeEnd(detail) { console.log("change end", detail) }, })
Changing the orientation
By default, the splitter is assumed to be horizontal. To change the orientation
to vertical, set the orientation property in the machine's context to
vertical.
const service = useMachine(splitter.machine, { // ... orientation: "vertical", })
Specifying constraints
Use the panels property to specify constraints like minSize and maxSize
for the splitter panels.
const service = useMachine(splitter.machine, { // ... panels: [ { id: "a", minSize: 20, maxSize: 80 }, { id: "b", minSize: 20, maxSize: 80 }, ], })
Configuring keyboard resize step
Use keyboardResizeBy to control how many pixels arrow keys resize by.
const service = useMachine(splitter.machine, { panels, keyboardResizeBy: 16, })
Setting the collapsed size
Set the collapsedSize and collapsible of a panel to specify the collapsed
size of the panel.
For best results, ensure you also set the
minSizeof the panel
const service = useMachine(splitter.machine, { // ... panels: [ { id: "a", collapsible: true, collapsedSize: 5, minSize: 10, maxSize: 20 }, { id: "b", minSize: 50 }, ], })
This allows the user to drag the splitter to collapse the panel to the
collapsedSize.
Listening for collapse events
When the splitter panel is collapsed, the onCollapse callback is invoked.
Alternatively, the onExpand callback is invoked when the panel is expanded.
const service = useMachine(splitter.machine, { // ... onCollapse(detail) { console.log("collapse", detail) }, onExpand(detail) { console.log("expand", detail) }, })
Programmatic panel control
Use the connected API to resize, collapse, or expand panels from code.
api.setSizes([30, 70]) api.resizePanel("a", 40) api.collapsePanel("a") api.expandPanel("a") api.resetSizes()
Styling guide
Each part includes a data-part attribute you can target in CSS.
Resize trigger
When a resize trigger is horizontal or vertical, a data-orientation attribute
is set on the trigger element.
[data-scope="splitter"][data-part="resize-trigger"] { /* styles for the item */ } [data-scope="splitter"][data-part="resize-trigger"][data-orientation="horizontal"] { /* styles for the item is horizontal state */ } [data-scope="splitter"][data-part="resize-trigger"][data-orientation="vertical"] { /* styles for the item is horizontal state */ } [data-scope="splitter"][data-part="resize-trigger"][data-focus] { /* styles for the item is focus state */ } [data-scope="splitter"][data-part="resize-trigger"]:active { /* styles for the item is active state */ } [data-scope="splitter"][data-part="resize-trigger"][data-disabled] { /* styles for the item is disabled state */ }
Methods and Properties
The splitter's api exposes the following methods and properties:
Machine Context
The splitter machine exposes the following context properties:
orientation"horizontal" | "vertical"The orientation of the splitter. Can be `horizontal` or `vertical`sizenumber[]The controlled size data of the panelsdefaultSizenumber[]The initial size of the panels when rendered. Use when you don't need to control the size of the panels.panelsPanelData[]The size constraints of the panels.onResize(details: ResizeDetails) => voidFunction called when the splitter is resized.onResizeStart() => voidFunction called when the splitter resize starts.onResizeEnd(details: ResizeEndDetails) => voidFunction called when the splitter resize ends.idsPartial<{ 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.keyboardResizeBynumberThe number of pixels to resize the panel by when the keyboard is used.noncestringThe 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.dir"ltr" | "rtl"The document's text/writing direction.idstringThe unique identifier of the machine.getRootNode() => ShadowRoot | Node | DocumentA root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.
Machine API
The splitter api exposes the following methods:
draggingbooleanWhether the splitter is currently being resized.orientation"horizontal" | "vertical"The orientation of the splitter.getSizes() => number[]Returns the current sizes of the panels.setSizes(size: number[]) => voidSets the sizes of the panels.getItems() => SplitterItem[]Returns the items of the splitter.getPanels() => PanelData[]Returns the panels of the splitter.getPanelById(id: string) => PanelDataReturns the panel with the specified id.getPanelSize(id: string) => numberReturns the size of the specified panel.isPanelCollapsed(id: string) => booleanReturns whether the specified panel is collapsed.isPanelExpanded(id: string) => booleanReturns whether the specified panel is expanded.collapsePanel(id: string) => voidCollapses the specified panel.expandPanel(id: string, minSize?: number) => voidExpands the specified panel.resizePanel(id: string, unsafePanelSize: number) => voidResizes the specified panel.getLayout() => stringReturns the layout of the splitter.resetSizesVoidFunctionResets the splitter to its initial state.getResizeTriggerState(props: ResizeTriggerProps) => ResizeTriggerStateReturns the state of the resize trigger.