Splitter
A splitter allow create dynamic layouts split into vertically or horizontally arranged panes. Panes are separated by the splitter bars that allow dragging to resize or expand/collapse them.
A
B
Features
- Built with flexbox for flexible layout and SSR
- Support both dynamic horizontal and vertical panels
- Support multiple panels and splitters
- Support for collapsible panels
- Support for panel constraints like min and max sizes
- Programmatic control of panel sizes
- Implements the Window Splitter pattern for accessibility and keyboard controls
Installation
To use the splitter machine in your project, run the following command in your command line:
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
This command will install the framework agnostic splitter logic and the reactive utilities for your framework of choice.
Anatomy
To set up the slider correctly, you'll need to understand its anatomy and how we name its parts.
Each part includes a
data-part
attribute to help identify them in the DOM.
Usage
First, import the splitter package into your project
import * as splitter from "@zag-js/splitter"
The splitter package exports two key functions:
machine
— The state machine logic for the splitter widget.connect
— The function that translates the machine's state to JSX attributes and event handlers.
You'll also need to provide a unique
id
to theuseMachine
hook. This is used to ensure that every part has a unique identifier.
Next, import the required hooks and functions for your framework and use the splitter machine in your project 🔥
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: [ { id: "a", size: 50 }, { id: "b", size: 50 }, ], }) 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: [ { id: "a", size: 50 }, { id: "b", size: 50 }, ], }) 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: [ { id: "a", size: 50 }, { id: "b", size: 50 }, ], }) 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: [ { id: "a", size: 50 }, { id: "b", size: 50 }, ], }) 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], })
Listening for resize events
When the resize trigger is dragged, the onResize
, onResizeStart
and
onResizeEnd
callback is invoked.
const service = useMachine(splitter.machine, { // ... onResize(detail) { console.log("resize", detail) }, onResizeStart(detail) { console.log("change start", detail) }, 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: 100, maxSize: 300 }, { id: "b", minSize: 100, maxSize: 300 }, ], })
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
minSize
of 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) }, })
Styling guide
Earlier, we mentioned that each accordion part has a data-part
attribute added
to them to select and style them in the DOM.
Resize trigger
When an splitter item is horizontal or vertical, a data-state
attribute is set
on the item and content elements.
[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`size
number[]
The controlled size data of the panelsdefaultSize
number[]
The initial size of the panels when rendered. Use when you don't need to control the size of the panels.panels
PanelData[]
The size constraints of the panels.onResize
(details: ResizeDetails) => void
Function called when the splitter is resized.onResizeStart
() => void
Function called when the splitter resize starts.onResizeEnd
(details: ResizeEndDetails) => void
Function called when the splitter resize ends.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
number
The number of pixels to resize the panel by when the keyboard is used.nonce
string
The nonce for the injected splitter cursor stylesheet.onCollapse
(details: ExpandCollapseDetails) => void
Function called when a panel is collapsed.onExpand
(details: ExpandCollapseDetails) => void
Function called when a panel is expanded.dir
"ltr" | "rtl"
The document's text/writing direction.id
string
The unique identifier of the machine.getRootNode
() => ShadowRoot | Node | Document
A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.
Machine API
The splitter api
exposes the following methods:
dragging
boolean
Whether the splitter is currently being resized.getSizes
() => number[]
The current sizes of the panels.setSizes
(size: number[]) => void
Set the sizes of the panels.getItems
() => SplitterItem[]
Get the items of the splitter.getPanelSize
(id: string) => number
Get the size of a panel.isPanelCollapsed
(id: string) => boolean
Whether a panel is collapsed.isPanelExpanded
(id: string) => boolean
Whether a panel is expanded.collapsePanel
(id: string) => void
Collapse a panel.expandPanel
(id: string, minSize?: number) => void
Expand a panel.resizePanel
(id: string, unsafePanelSize: number) => void
Resize a panel.getLayout
() => string
Get the layout of the splitter.
Data Attributes
Edit this page on GitHub