sveltelean Primitives
Versionv0.2.0 GitHub

Example

A file list and an editor

aria-valuenow = 30

Drag the line between the panes, or focus it and use the arrow keys.

<div data-slean="splitter" style="--slean-split: 30">
	<nav id="splitter-files" data-slean-part="pane" aria-label="Files">…</nav>
	<div
		role="separator"
		tabindex="0"
		aria-label="Files"
		aria-controls="splitter-files"
		aria-orientation="vertical"
		aria-valuenow="30"
		aria-valuemin="15"
		aria-valuemax="60"
		data-slean-part="handle"
	></div>
	<section data-slean-part="pane" aria-label="Editor">…</section>
</div>
The size is a percentage of the root, bounded by aria-valuemin and aria-valuemax. Enter collapses the file list; Enter again restores it.

Why this implementation exists

CSS resize resizes one box by a corner grip, with no keyboard and nothing for assistive technology. The WAI-ARIA window splitter is the accessible form: a focusable separator with a value, moved by the arrow keys and by the pointer.

The value is the state. aria-valuenow on the handle and --slean-split on the root carry the same number, and the stylesheet turns the property into the first grid track, so a split rendered on the server is right before the script loads. The keyboard reads and writes the attribute and keeps nothing. A drag is the one moment that needs memory: pointer capture and four listeners on the handle, created on pointerdown and removed on release. That scoped lifetime is what Tier 2 means here.

The browser owns

  • focus on the handle and the keydown and pointerdown events routed to the behavior
  • pointer capture: the handle keeps receiving the drag outside its own box
  • the grid layout that turns one number into two panes

Svelte Lean owns

  • the WAI-ARIA window splitter keyboard: arrows by a step, Home, End, Enter to collapse and restore
  • a pointer drag whose listeners live exactly as long as the drag
  • aria-valuenow and --slean-split kept equal, bounded by aria-valuemin and aria-valuemax
  • slean:resize after every change; RTL and stacked panes
  • splitter.css: the grid, the handle line and grip, focus and drag states

Usage

Install @svelte-lean/primitives for the behavior and @svelte-lean/styles for the stylesheet. With @svelte-lean/vite the registration is injected for every static data-slean="splitter"; without it, import the register module once.

npm install @svelte-lean/primitives
+layout.svelte or any client module
import '@svelte-lean/primitives/splitter/register';
stylesheets
import '@svelte-lean/styles/splitter.css';

Render the root with --slean-split, two pane parts and the handle between them with aria-valuenow, aria-valuemin, aria-valuemax, aria-controls and a name, usually the first pane’s.

Listen to slean:resize to store the size, and render it back on the next visit through the same two attributes.

Anatomy

PartElementdata-slean-partRequiredNotes
root<div data-slean="splitter" style="--slean-split: 30">–yesOptions: data-slean-orientation (horizontal, vertical), data-slean-step (default 5).
paneany elementpaneyesExactly two. The first is the one the value measures.
handle<div role="separator" tabindex="0" aria-valuenow aria-valuemin aria-valuemax aria-controls aria-label>handleyesaria-orientation="vertical" for side-by-side panes, "horizontal" for stacked ones.

Runtime profile

The splitter registers one keydown and one pointerdown handler with the shared router. During a drag, and only then, the handle holds four listeners and the root's box. A thousand splitters keep one listener per type (tests/splitter.test.ts).

Accessibility contract

  • The handle is a separator with a value, bounds and an orientation, named after the pane it resizes and pointing at it with aria-controls.
  • Arrow keys move it by data-slean-step; Home and End go to the bounds; Enter collapses and restores the first pane.
  • Under dir="rtl" the first pane is on the right: the drag measures from the right edge and ArrowLeft enlarges it.
  • A drag focuses the handle, so the keyboard continues from where the pointer left it.

Keyboard

KeyWhenResult
ArrowLeft/ArrowRightfocus on the handle, panes side by sideSmaller or larger first pane by the step; swapped under dir="rtl"
ArrowUp/ArrowDownfocus on the handle, panes stackedSmaller or larger first pane by the step
Home/Endfocus on the handleMinimum or maximum size
Enterfocus on the handleCollapses the first pane to its minimum; again restores it

Platform features

FeatureBaselineOutside the target
Pointer events, pointer captureWidely availableNot applicable
touch-actionWidely availableNot applicable

Without JavaScript

The panes render at the server's split and cannot be resized.

Server rendering

Render the same value in aria-valuenow and in the root's --slean-split. Nothing is measured on the server. A Content Security Policy without style-src-attr 'unsafe-inline' blocks the style attribute: the panes then start at an even split until the first key or drag, since the behavior writes through the CSSOM.

Before hydration

Before the behavior loads, the handle is focusable but neither keys nor drags move it. The first event after hydration works on the server's markup.

Styling

splitter.css lays the root out as a grid whose first track is --slean-split percent, draws the handle as a line with a grip that appears on hover, focus and drag, and widens it for coarse pointers. The tokens it reads:

TokenDefault (light)Applies to
--slean-split50grid-template-columns, grid-template-rows
--slean-space-20.5remgrid-template-columns, grid-template-rows
--slean-bordervar(--slean-neutral-6)border, background
--slean-radius-md0.625remborder-radius
--slean-surfaceoklch(100% 0 0)background
--slean-fgvar(--slean-neutral-12)color
--slean-duration-fast100mstransition
--slean-easecubic-bezier(0.2, 0, 0, 1)transition
--slean-radius-full9999pxborder-radius
--slean-border-strongvar(--slean-neutral-8)background
--slean-accentoklch(54% 0.19 258)background
--slean-focus-ringvar(--slean-focus-ring-width) solid var(--slean-focus-ring-color)outline
--slean-focus-ring-width2pxoutline-offset
--slean-space-51.25remgrid-template-columns, grid-template-rows

State selectors the stylesheet targets, all from the platform or ARIA: :focus-visible, :hover, [aria-disabled="true"].

Variant attributes: data-slean-orientation (vertical); data-slean-dragging.

Compatibility notes

Pointer events, pointer capture, touch-action and CSS grid are widely available. More than two panes, pixel sizes and persistence are not part of the primitive; nest splitters and store the value from slean:resize.

Examples

Stacked panes

data-slean-orientation="vertical" stacks the panes. The separator's own aria-orientation is then horizontal, and ArrowUp and ArrowDown move it. Give the root a height.

Query

Results

stacked
<!-- Stacked panes: give the root a height; the separator is horizontal. -->
<div data-slean="splitter" data-slean-orientation="vertical" style="--slean-split: 60; block-size: 20rem">
	<section data-slean-part="pane">…</section>
	<div role="separator" tabindex="0" aria-orientation="horizontal" aria-valuenow="60" …></div>
	<section data-slean-part="pane">…</section>
</div>

Testing

Source