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'));
$ node demo.js
Hello, Pure Admin!
$ npm test
✓ 42 tests passed
✓ 0 failures
$  

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):

AttributeDefaultDescription
data-pa-splitterMarker. Required.
data-pa-splitter-idnoneEnables persistence under pa-splitter:<id>
data-pa-splitter-step10Keyboard step in px
data-pa-splitter-rail-size40Rail width in px (minimized state)
data-pa-splitter-minimize-threshold0.40Drag snaps to rail below this fraction of the minimized side's natural min

On root (legacy 2-pane only):

AttributeDefaultDescription
data-pa-splitter-min-start0px or %
data-pa-splitter-max-startavailablepx or %
data-pa-splitter-default30%Initial size if no saved state
data-pa-splitter-minimizeoff"start" or "end" opts into rail collapse on that side

On each pane (N-pane only):

AttributeDefaultDescription
data-pa-splitter-sizesharedInitial size (px or %). Unsized panes split the leftover.
data-pa-splitter-min0px or %
data-pa-splitter-maxavailablepx or %
data-pa-splitter-minimizeMarker. Only honoured on the first and last panes.

Keyboard

KeyAction
Move boundary toward start (shrink left pane)
Move boundary toward end (grow left pane)
HomeJump left pane to its minimum
EndJump left pane to its maximum
Enter / SpaceToggle 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.

Type / for commands, : to search a category, or just start typing

Settings

Body text size. All elements scale proportionally.
👤

John Doe

Administrator
  • 📊 Dashboard
  • 📝 Forms
  • 📋 Tables
  • 📊 Data Grid