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 by putting data-pa-splitter-minimize on the pane you want collapsible (here, the first pane). The gutter's collapse action (double-click, Enter/Space, or the toggle button in the header) toggles the pane to a thin rail with a vertical card title — instead of disappearing. Click the rail 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. Dragging the gutter inward while the pane is already rail'd is inert — only outward drags (the direction that would grow the pane) release the rail.
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 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)
// - drag the gutter outward past the snap threshold
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
Put data-pa-splitter-minimize on the last pane to collapse it to a vertical-title rail on the right edge. Same restore paths as collapsing the first pane (rail click, 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
Per-pane data-pa-splitter-size / -min / -max drive sizing; any pane with data-pa-splitter-minimize can collapse to a rail. 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
<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. Each pane carries its own data-pa-splitter-size / -min / -max. Any pane with data-pa-splitter-minimize can collapse to a rail — first / last panes dock against the container edge; middle panes collapse in place and split released slack between both neighbours. Panes without an explicit data-pa-splitter-size share leftover space equally.
Data attributes
On root:
| 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 each pane:
| 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. Pane can collapse to a rail (first / last dock at the edge; middle panes collapse in place). |
Accordion mode (auto):
When the container is too narrow to fit all panes at their minimums AND there are 2+ minimizable panes, the splitter switches to single-pane-expanded mode — restoring one pane auto-rails the others. Engages and disengages automatically as the container resizes; adds pa-splitter--accordion to the root as a styling hook.
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.