npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@magic-spells/split-panel

v0.1.0

Published

Resizable split panel web component — draggable dividers, horizontal/vertical splits, nesting, min/max constraints, localStorage persistence, no dependencies

Readme

Split Panel

Resizable split-panel layout web component. Draggable dividers between panels, horizontal or vertical splits, nesting, pixel/percent min/max constraints, keyboard accessibility, and optional localStorage persistence. 3.2 kB JS gzip, 0.7 kB CSS gzip, zero dependencies, no Shadow DOM.

Demo

Install

npm install @magic-spells/split-panel
import '@magic-spells/split-panel';
import '@magic-spells/split-panel/css';

Or via CDN:

<link rel="stylesheet" href="https://unpkg.com/@magic-spells/split-panel/dist/split-panel.min.css" />
<script src="https://unpkg.com/@magic-spells/split-panel"></script>

Usage

<split-panel-group>
	<split-panel size="30" min="200px">Sidebar</split-panel>
	<split-panel>Main</split-panel>
</split-panel-group>

The group lays out its direct <split-panel> children with flexbox and generates a draggable <split-divider> between each adjacent pair — you never write dividers by hand. Any number of panels works; each divider resizes only its two neighbors.

The group fills its container (width: 100%; height: 100%), so give the container an explicit size.

Vertical

<split-panel-group direction="vertical">
	<split-panel>Top</split-panel>
	<split-panel>Bottom</split-panel>
</split-panel-group>

Nested

A panel can contain another group. Each group only manages its own direct children:

<split-panel-group>
	<split-panel size="35">Sidebar</split-panel>
	<split-panel size="65">
		<split-panel-group direction="vertical">
			<split-panel size="60">Editor</split-panel>
			<split-panel size="40">Console</split-panel>
		</split-panel-group>
	</split-panel>
</split-panel-group>

Saved positions

Give a group an id and committed sizes persist to localStorage (key split-panel:<id>) and restore on load:

<split-panel-group id="workspace">
	<split-panel>…</split-panel>
	<split-panel>…</split-panel>
</split-panel-group>

Attributes

<split-panel-group>

| Attribute | Description | | ----------- | --------------------------------------------------------------------------------------- | | direction | horizontal (default) — side-by-side columns; vertical — stacked rows | | disabled | Locks all resizing — divider handles are hidden and stop responding to pointer/keyboard | | snap | On release, settle the divider to the nearest listed point. Space/comma-separated percentages of the divider's travel (0 = previous panel at its min, 100 = at its max). Bare snap = 0 50 100 | | id | Opts into localStorage persistence |

<split-panel>

| Attribute | Description | | --------- | ---------------------------------------------------------------------------------------------------- | | size | Initial size — a percent (30 or 30%) or a pixel value (250px). Panels without a size split the remainder equally | | min | Minimum size — 200px or a bare number for pixels; 15% for a percent | | max | Maximum size — same formats as min |

<split-divider> (generated)

| Attribute | Description | | ---------- | ------------------------- | | disabled | Locks just this divider — hides its handle |

Methods

| Method | Description | | ----------------- | ------------------------------------------------------------------------------- | | sizes | Getter — current sizes as an array of percentages summing to 100 | | setSizes(array) | Sets sizes programmatically (one number per panel, normalized to 100), commits | | resetSizes() | Restores the initial authored sizes | | disabled | Getter/setter reflecting the disabled attribute |

Events

Both bubble and are dispatched on the group.

| Event | When | detail | | ----------------------- | --------------------------------------------------------------- | -------------------------------------------- | | split-panel:resize | Live, on every applied change during a drag or key press | { sizes: number[], divider: number } | | split-panel:resize-end | Commit — drag end, each key press, reset, or programmatic change | { sizes: number[], divider: number \| null } |

Keyboard

Dividers are focusable role="separator" elements (WAI-ARIA window-splitter pattern).

| Key | Action | | ------------------- | -------------------------------------------------- | | / (horizontal) or / (vertical) | Resize by 1% | | Shift + arrow | Resize by 10% | | Home / End | Previous panel to its minimum / maximum | | Enter | Reset the pair to initial sizes (like double-click) |

Styling

| Custom property | Default | Description | | ------------------------------------- | --------- | ------------------------------------ | | --split-panel-gap | 0.5rem | Gutter width (the divider itself) | | --split-panel-divider-color | #ddd | Divider line | | --split-panel-divider-hover-color | #bbb | Divider line on hover | | --split-panel-divider-active-color | #4299e1 | Divider line while dragging | | --split-panel-divider-line | 0.125rem| Visual line thickness | | --split-panel-divider-length | 100% | Visual line length — shorten (e.g. 60% or 2.5rem) for a centered handle/tab; the full gutter stays draggable | | --split-panel-divider-radius | 0.0625rem| Visual line corner radius | | --split-panel-focus-ring-color | #4299e1 | Keyboard focus ring | | --split-panel-transition-duration | 120ms | Divider color transition | | --split-panel-animate-duration | 0s | Ease duration for programmatic/snap/keyboard size changes (live dragging stays instant) | | --split-panel-animate-easing | ease-in-out | Timing function for the size transition |

Panels are sized by a --split-panel-size custom property the group writes (flex: var(--split-panel-size, 1) 1 0%). For zero-flash first paint with authored sizes, you can set it inline yourself: <split-panel size="30" style="--split-panel-size: 30">.

--split-panel-visible (reveal effects)

Any panel with a max also gets a read-only --split-panel-visible custom property — a 01 value tracking where the panel sits in its min/max range (1 at max, 0 at min). It updates live during drags, on window resize, and inside nested groups, so you can drive fade/scale/blur effects as a panel shrinks toward hidden:

split-panel[max] .content {
	opacity: var(--split-panel-visible, 1);
	transform: scale(calc(0.8 + 0.2 * var(--split-panel-visible, 1)));
	filter: blur(calc((1 - var(--split-panel-visible, 1)) * 6px));
}

To let a panel collapse all the way to nothing, keep its padding on an inner wrapper (a padded panel can't shrink below its own padding). Most useful with pixel min/max.

Animating size changes

Set --split-panel-animate-duration to ease programmatic (setSizes/resetSizes), snap, and keyboard changes. Live dragging is always instant, and the authored layout is never animated in on first load:

split-panel-group {
	--split-panel-animate-duration: 320ms;
	--split-panel-animate-easing: ease-in-out;
}

Combined with snap, releasing a drag glides to the nearest snap point — and because --split-panel-visible updates throughout the transition, any fade/scale effect rides along with it.

License

MIT