Resizable container with two or more panes and draggable gutters. Drag to resize, double-click a gutter to collapse / restore, and use arrow keys (or Home / End) when a gutter is focused. Sizes can be constrained per pane and persisted to localStorage.
Horizontal split — side-by-side, vertical gutter
Sidebar + main content. Start pane is constrained to min: 200px and max: 60%, with a default of 280px. Size persists to localStorage under the id demo-horizontal.
Files
- 📁 components/
- 📁 utils/
- 📄 main.scss
- 📄 README.md
- 📄 package.json
- 📄 .gitignore
- 📄 LICENSE
main.scss
Drag the gutter, double-click it to collapse, or focus it and use arrow keys.
@use 'variables/index' as *;
@use 'core' as *;
@use 'utilities' as *;
:root {
@include output-base-css-variables;
@include output-pa-css-variables;
}
Vertical split — stacked, horizontal gutter
Editor over console. Constraints: min: 80px, max: 80%, default 60%. Persists under demo-vertical.
Editor
function greet(name) {
return `Hello, ${name}!`;
}
console.log(greet('Pure Admin'));
Spaced cards — gutter with breathing room
Use native gap on the splitter root to add space between the panes and the gutter — the JS subtracts it from the available space so percent constraints stay accurate. A thicker gutter is opt-in via --pa-splitter-gutter-size.
Left card
This pane holds a card. The flex gap on the splitter keeps the card from touching the gutter — no per-pane padding hack needed.
Right card
Drag the gutter — both cards reflow. The 10px gutter is set inline via --pa-splitter-gutter-size; the default is 6px.
Minimize to rail — vertical header instead of collapse
Opt in with data-pa-splitter-minimize="start". The gutter's collapse action (double-click, Enter/Space, the toggle button in the header, or pressing the gutter while minimized) toggles the start pane to a thin rail with a vertical card title — instead of disappearing. Click the rail or press the gutter to restore. Rail width is configurable via data-pa-splitter-rail-size (default 40px). You can also just drag the gutter inward — once the requested width drops below 40% of the minimized side's natural min (configurable via data-pa-splitter-minimize-threshold) it snaps to rail; dragging back outward across the same threshold pops it back to min.
Bonus: the Editor card's actions use the new pa-card__actions--responsive pattern — drag the gutter rightward to shrink the Editor pane, and once the header drops below $card-actions-collapse-at (28rem ≈ 280px) the spread buttons collapse into a single split button. Pure CSS container query, no JS observer.
File explorer
- 📁 src/
- 📁 dist/
- 📄 main.scss
- 📄 splitter.js
- 📄 README.md
Click the chevron in the header to minimize. Click the rail (or press the gutter) to restore.
Editor
When the file explorer minimizes, this card stays put and the gutter slides next to the rail.
// Toggle the minimize state:
// - double-click the gutter
// - focus gutter + press Enter / Space
// - click the rail (while minimized)
// - press the gutter (while minimized)
Progressive overflow — buttons drop into a "More" menu one at a time
A second collapse model, complementing --responsive. Mark the actions wrapper with pa-card__actions--overflow and the JS module (card-actions-overflow.js) measures each button on init. A ResizeObserver watches the wrapper; when the row can't fit, the lowest-priority button moves into a "..." menu (appended to <body> so card overflow: hidden can't clip it).
- Priority via
data-pa-actions-priority="N"— higher stays longer. - Tiebreak direction via
data-pa-actions-overflow-from="end"(default — rightmost drops first) or"start"(leftmost drops first).
Editor
Shrink me — buttons collapse into the "..." menu by ascending priority. Run has data-pa-actions-priority="10" and stays last.
Drag the gutter left/right to shrink and grow the editor card. Watch the action row in its header.
Minimize end pane to rail — right-edge inspector pattern
Set data-pa-splitter-minimize="end" to collapse the right pane to a vertical-title rail on the right edge. Same restore paths as the start variant (rail click, gutter press, toggle button, dblclick, Enter/Space, or drag the gutter outward past the threshold). Useful for inspector / detail / properties panels.
Editor
Drag the gutter to the right (shrinking the inspector) — once the inspector's width drops below 75% of its implied min, it snaps to a rail on the right edge.
// The inspector on the right is the
// rail-side here. Press the rail, the
// gutter, or the toggle button to
// restore it.
Inspector
Properties, outlines, references, etc. live here. Click the chevron, double-click the gutter, or drag the gutter rightward to collapse.
N panes — pick a count, both edges minimizable
N-pane markup uses per-pane data-pa-splitter-size / -min / -max attributes instead of root-level -min-start / -max-start. The first and last panes opt into rail collapse with data-pa-splitter-minimize (a marker — no start/end value needed, the edge is implied by position). Middle panes can't minimize. Drag any gutter and either neighbour will snap to rail below the threshold. Each pane count remembers its own layout in localStorage.
localStorage persistence
Both demos above persist their size under pa-splitter:<id>. Reload the page — your drag positions stick. Clear them with the button below.
Markup — two panes (legacy shorthand)
<div class="pa-splitter pa-splitter--horizontal"
data-pa-splitter
data-pa-splitter-id="my-id"
data-pa-splitter-min-start="200px"
data-pa-splitter-max-start="60%"
data-pa-splitter-default="280px">
<div class="pa-splitter__pane
pa-splitter__pane--start">
A
</div>
<div class="pa-splitter__gutter"
role="separator"
aria-orientation="vertical"
tabindex="0"></div>
<div class="pa-splitter__pane
pa-splitter__pane--end">
B
</div>
</div>
Triggered by the --start / --end modifiers. The end pane fills the leftover via flex: 1; only the start pane carries an explicit size.
Markup — N panes
<div class="pa-splitter pa-splitter--horizontal"
data-pa-splitter
data-pa-splitter-id="my-id">
<div class="pa-splitter__pane"
data-pa-splitter-size="240px"
data-pa-splitter-min="180px"
data-pa-splitter-minimize>A</div>
<div class="pa-splitter__gutter"
role="separator" tabindex="0"></div>
<div class="pa-splitter__pane"
data-pa-splitter-min="240px">B</div>
<div class="pa-splitter__gutter"
role="separator" tabindex="0"></div>
<div class="pa-splitter__pane"
data-pa-splitter-size="280px"
data-pa-splitter-minimize>C</div>
</div>
Any N ≥ 2. Panes and gutters must alternate. Per-pane data-pa-splitter-size / -min / -max replace the root-level -start attributes. Only pane 0 and pane N-1 honour data-pa-splitter-minimize. Panes without an explicit data-pa-splitter-size share leftover space equally.
Data attributes
On root (both modes):
| Attribute | Default | Description |
|---|---|---|
data-pa-splitter | — | Marker. Required. |
data-pa-splitter-id | none | Enables persistence under pa-splitter:<id> |
data-pa-splitter-step | 10 | Keyboard step in px |
data-pa-splitter-rail-size | 40 | Rail width in px (minimized state) |
data-pa-splitter-minimize-threshold | 0.40 | Drag snaps to rail below this fraction of the minimized side's natural min |
On root (legacy 2-pane only):
| Attribute | Default | Description |
|---|---|---|
data-pa-splitter-min-start | 0 | px or % |
data-pa-splitter-max-start | available | px or % |
data-pa-splitter-default | 30% | Initial size if no saved state |
data-pa-splitter-minimize | off | "start" or "end" opts into rail collapse on that side |
On each pane (N-pane only):
| Attribute | Default | Description |
|---|---|---|
data-pa-splitter-size | shared | Initial size (px or %). Unsized panes split the leftover. |
data-pa-splitter-min | 0 | px or % |
data-pa-splitter-max | available | px or % |
data-pa-splitter-minimize | — | Marker. Only honoured on the first and last panes. |
Keyboard
| Key | Action |
|---|---|
| ← ↑ | Move boundary toward start (shrink left pane) |
| → ↓ | Move boundary toward end (grow left pane) |
| Home | Jump left pane to its minimum |
| End | Jump left pane to its maximum |
| Enter / Space | Toggle minimize on the nearest minimizable neighbour |
Each gutter handles its own keyboard input. Tab focuses the next gutter; the "left pane" is whichever pane sits before the focused gutter.
JavaScript API
Auto-initializes on [data-pa-splitter] at DOMContentLoaded. For dynamically inserted splitters:
// Init a single element
PaSplitter.init(document.getElementById('my-splitter'));
// Init all uninitialized splitters in a subtree
PaSplitter.initAll(someContainer);
init() is idempotent — calling it twice on the same element is a no-op.