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.
Install
Section titled “Install”pnpm add @agencecinq/windowsplitterImport 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, andaria-controlsin 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 documentsrole="separator". Some screen readers handle a focusablerole="slider"more reliably. The component accepts either on the nested control — pick what works for your AT testing.
Required markup
Section titled “Required markup”| 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. |
Options
Section titled “Options”| 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} %`;Methods
Section titled “Methods”| 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.
Properties
Section titled “Properties”| 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 |
Styling hooks
Section titled “Styling hooks”| 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 |
Events
Section titled “Events”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);});Keyboard
Section titled “Keyboard”| 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).
Playground (live)
Section titled “Playground (live)”Dungeon map (vertical)
mode: 'resize' — drag or arrow keys resize the primary pane.Enter collapses / restores.
Primary pane — sized by the splitter value.
Secondary pane fills the remaining space.
—
Spellbook (horizontal)
aria-orientation="horizontal" — Up / Down move the bar;Enter collapses the top pane.
Primary pane — height follows the splitter value.
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).
—