Skip to content

Window Splitter

A window splitter lets users resize a primary pane with a focusable separator. <cinq-windowsplitter> is the layout wrapper (bounds + CSS custom properties). A nested [role="separator"] (or slider) is the interactive control: the component wires pointer drag, keyboard steps, collapse / restore, and layout updates (resize or clip).

Follows the WAI-ARIA Authoring Practices window splitter pattern. Inspired by @19h47/windowsplitter.

Terminal window
pnpm add @agencecinq/windowsplitter

Import once:

import "@agencecinq/windowsplitter";

Then wrap panes and a separator — the host owns layout context; ARIA lives on the separator.

HTML is the source of truth. The component will not invent missing attributes or warn about missing labels. Set role, tabindex, aria-orientation, value attributes, labelling, and aria-controls in HTML. Use an a11y linter (axe, Lighthouse) to catch invalid markup.

<cinq-windowsplitter class="panes">
<div id="toc" class="pane pane--primary" style="width: 30%">
<strong id="toc-label">Table of Contents</strong>
</div>
<div
role="separator"
tabindex="0"
aria-orientation="vertical"
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="30"
aria-controls="toc"
aria-labelledby="toc-label"
></div>
<div class="pane pane--secondary">Content</div>
</cinq-windowsplitter>

The primary pane is resolved live via Element.ariaControlsElements on the separator.

aria-valuemin / aria-valuemax must match the real allowed range of the primary pane. If the pane cannot fully collapse, set min above 0.

Note on role: the APG documents role="separator". Some screen readers handle a focusable role="slider" more reliably. The component accepts either on the nested control — pick what works for your AT testing.

Selector / attribute Required Role
<cinq-windowsplitter> Yes Layout wrapper / bounds container.
Nested [role="separator"] or [role="slider"] Yes Focusable control ($separator).
tabindex="0" Yes On the separator.
aria-orientation Yes On the separator: vertical or horizontal.
aria-valuemin / aria-valuemax / aria-valuenow Yes On the separator.
aria-controls Yes (for resize / clip) On the separator: id of the primary pane.
aria-label / aria-labelledby Yes On the separator.
Attribute / property Type Default Description
data-windowsplitter-mode resize | clip | none resize How the primary pane is updated
data-windowsplitter-step number 1 Arrow key increment
data-windowsplitter-page number 10 PageUp / PageDown / Shift+Arrow
data-windowsplitter-fixed boolean false Enter / pointer toggle only
disabled / aria-disabled boolean false Disable interaction (host)
formatSize (property) FormatSize `${value}%` CSS size in resize mode
formatValue (property) FormatValue String(value) aria-valuetext
const host = document.querySelector("cinq-windowsplitter");
host.formatSize = ({ offset }) => `${offset}px`;
host.formatValue = (value) => `${value} %`;
Method Description
setValue(value, trigger?) Set aria-valuenow on the separator and update layout
collapse(trigger?) / restore(trigger?) / toggle(trigger?) Collapse helpers (history only from collapse / Enter)
sync() Re-read ARIA and re-apply layout
destroy() Remove listeners and observers. Called automatically from disconnectedCallback.

setValue / collapse / restore / toggle take an optional trigger flag (default true) to control whether windowsplitter:change is dispatched.

Property Description
$separator Focusable separator control
$primary Primary pane from ariaControlsElements (live)
value / min / max / ratio / orientation Read from separator ARIA
collapsed / disabled Derived state
mode / step / page / fixed Options
formatSize / formatValue Formatters
Hook When
[collapsed] Primary pane at aria-valuemin (host)
[dragging] Pointer drag in progress (host)
[disabled] / [aria-disabled="true"] Interaction disabled
:focus-visible on the separator Keyboard focus
--windowsplitter-value / --windowsplitter-ratio / --windowsplitter-offset On the host

Dispatched on the host <cinq-windowsplitter> (bubble). Prefer constants from @agencecinq/utils EVENTS:

Event Constant Detail When
windowsplitter:change WINDOWSPLITTER_CHANGE { value, min, max, ratio, collapsed } After a user-driven (or trigger: true) value change
import { EVENTS } from "@agencecinq/utils";
$host.addEventListener(EVENTS.WINDOWSPLITTER_CHANGE, ({ detail }) => {
console.log(detail.value, detail.collapsed);
});
Key Function
/ Move a vertical splitter
/ Move a horizontal splitter
Enter Collapse / restore
Home / End Jump to min / max
PageUp / PageDown Step by page
Shift + Arrow Step by page

A fixed splitter (data-windowsplitter-fixed) omits arrow keys and only implements Enter (and pointer toggle).

Dungeon map (vertical)

mode: 'resize' — drag or arrow keys resize the primary pane.Enter collapses / restores.

Party roster

Primary pane — sized by the splitter value.

Encounter notes

Secondary pane fills the remaining space.

Spellbook (horizontal)

aria-orientation="horizontal" — Up / Down move the bar;Enter collapses the top pane.

Prepared spells

Primary pane — height follows the splitter value.

Spell description

Secondary pane fills the remaining space below.

Before / after (clip)

data-windowsplitter-mode="clip" — overlay panes and clip the primary layer (same interaction as a comparison slider).

Before