Skip to main content

Splitter

Info:Layout html svelte astro Success: coverage 14/14

A draggable divider that resizes an adjacent pane, with configurable bounds and steps.

Live demo

live · @xoji/astro

Splitter

Resizable rail

Drag the divider, or focus it and use the arrow keys (Home and End jump to the bounds). The rail clamps to 160–420px and snaps in steps of 8.

Rail

240px

Content pane — it takes whatever the rail leaves.

Divider line & sizes

line paints a full-length divider so the handle reads on its own, not off the panes beside it; size sets the handle's own thickness (sm / md / lg).

Rail

A wide, self-drawn divider line.

Stacked (horizontal)

A horizontal divider resizes height instead of width.

Top

Bottom pane.

Splitter is the handle between two panes: drag it (or arrow it with the keyboard) and the neighboring pane grows or shrinks. It is a role="separator" control, so it announces its current, minimum, and maximum size and takes the full keyboard.

The size it manages is _data_ the consumer owns: the splitter clamps a value (in px) to min/max, snaps it to an integral step, writes it into a CSS custom property (var) on a target so a grid or flex track resizes declaratively, and fires resize (live, during the drag) and resize-end (on release) so the consumer can react or persist. orientation picks the axis (vertical resizes width, horizontal resizes height), and reversed flips the direction for a trailing-edge pane like a right rail. Its own chrome is derived: the grip and its focus ring read from the same tokens the rest of the UI does.

When to use

How this component composes with the rest of the set.

Place a splitter as a track between two panes in a grid or flex layout, sized by the var it writes.
Give AppShell a resizable rail and it drops a splitter between the rail and main for you.
Listen for resize-end to persist a chosen size; resize for a live preview.

Props

12 props, straight from the manifest.

PropTypeDefaultBindingsDescription
value number
html svelte astro
The current size of the managed pane in px. Controlled: the consumer owns it and updates on `resize`.
orientation SplitterOrientation
vertical horizontal
vertical
html svelte astro
The separator axis: `vertical` resizes width (a side rail), `horizontal` resizes height (a stacked pane).
size SplitterSize
sm md lg
md
html svelte astro
The handle's own thickness and grip: `sm`, `md`, or `lg`. This is the divider's footprint, independent of the pane size it controls.
line boolean false
html svelte astro
Paint a full-length divider line down the handle, so the splitter reads as a visible separator on its own instead of relying on the contrast between the panes it sits between.
min number 0
html svelte astro
The smallest the managed pane may shrink to, in px.
max number
html svelte astro
The largest the managed pane may grow to, in px. Unbounded when omitted.
step number 1
html svelte astro
The integral increment the size snaps to; drag and arrow keys both quantize to it.
default number
html svelte astro
A size to reset to on double-click. Omit to disable the reset affordance.
reversed boolean false
html svelte astro
Flip the drag direction, for a pane on the trailing edge (a right or bottom rail) where dragging toward the center grows it.
var string --xoji-splitter-size
html svelte astro
The CSS custom property the splitter writes the size into, so a grid or flex track can read it. Set on the target.
for string
html svelte astro
The id of the element the `var` is set on. Defaults to the splitter's parent, so a splitter inside the layout container just works.
disabled boolean false
html svelte astro
Lock the size. The handle stops responding to drag and keys and dims.

Appearance

Sizes

sm

.xoji-splitter--sm

A slim divider for dense layouts.

md

default
.xoji-splitter

The default divider footprint.

lg

.xoji-splitter--lg

A wide divider that's easy to grab.

States

focus-visible

.xoji-splitter:focus-visible

The handle focused by keyboard: a ring picks it out, the grip tints to accent.

disabled

.xoji-splitter--disabled

A locked splitter: the grip dims and the resize cursor drops.

Anatomy

The named parts that make up the component, with their selectors.

splitter

.xoji-splitter

The separator handle: a fixed-size cell in the layout track that captures the drag.

--space-3

grip

.xoji-splitter__grip

The visible grab affordance, accent-tinted on hover and focus.

--line --accent --radius-sm --border-thick --space-6 --duration-fast --ease-standard

Tokens & coverage

What the component consumes, checked live against what the algorithm produces.

Success:fully covered 14/14 consumed tokens produced default register: 276 tokens

Live coverage check against the xoji-default register (derive(xojiDefault, { anchors })coverComponent(manifest, register)). Every token this component consumes must be a key the algorithm produces.

--accent --border-normal --border-thick --border-thin --duration-fast --ease-standard --line --radius-sm --ring --space-2 --space-3 --space-5 --space-6 --space-8

Accessibility

The handle is a role="separator" with aria-valuenow/aria-valuemin/aria-valuemax, so assistive tech announces the size and its bounds.
It is fully keyboard-operable: arrow keys step by step, PageUp/PageDown jump ten steps, Home and End go to the bounds.
Give it a label (or labelledby) so the separator is announced by what it resizes, not as an unnamed divider.

Code

A resizable rail

A splitter between a side rail and the content, writing the rail width into a CSS variable the grid reads.

<div class="layout" style="display:grid;grid-template-columns:var(--rail,16rem) auto minmax(0,1fr);height:20rem">
	<aside class="panel">Rail</aside>
	<xoji-splitter var="--rail" value="256" min="160" max="480" step="8" label="Resize rail"></xoji-splitter>
	<main class="panel">Content</main>
</div>

<script>
	document.querySelector("xoji-splitter")
		.addEventListener("resize", (e) => console.log(e.detail.value));
</script>
<script lang="ts">
	import { Splitter } from "@xoji/svelte";
	let rail = $state(256);
</script>

<div style={`display:grid;grid-template-columns:${rail}px auto minmax(0,1fr);height:20rem`}>
	<aside>Rail</aside>
	<Splitter value={rail} min={160} max={480} step={8}
		onresize={(e) => (rail = e.detail.value)} label="Resize rail" />
	<main>Content</main>
</div>
---
import Splitter from "@xoji/astro/Splitter.astro";
---

<div style="display:grid;grid-template-columns:var(--rail,16rem) auto minmax(0,1fr);height:20rem">
	<aside>Rail</aside>
	<Splitter var="--rail" value={256} min={160} max={480} step={8} label="Resize rail" />
	<main>Content</main>
</div>