@magic-spells/panel-stack

Navigation that flows.

A nested-panel stack web component. Push, pop, drift gently between levels — with motion blur, depth, and a tiny API.

Install npm i @magic-spells/panel-stack
JS (gzip) 1.25 KB
CSS (gzip) 0.78 KB
Dependencies 0
// install npm i @magic-spells/panel-stack // import (auto-registers <panel-stack> and <stack-panel>) import '@magic-spells/panel-stack'; // markup <panel-stack initial="root"> <stack-panel handle="root"> <button data-action-stack-push target="shop">Shop</button> </stack-panel> <stack-panel handle="shop"> <button data-action-stack-pop>Back</button> </stack-panel> </panel-stack>

The classic — a mobile menu.

Tap a row with a chevron to drill in. The current panel slides off, the next slides in, and the inactive ones blur slightly into the background. Hit the back arrow to unwind one level at a time.

<panel-stack initial="menu-root"> <stack-panel handle="menu-root"> <button data-action-stack-push target="menu-shop">Shop</button> </stack-panel> <stack-panel handle="menu-shop"> <button data-action-stack-pop>Back</button> </stack-panel> </panel-stack>

Settings, the way they should feel.

Same component, different content. Here panels carry icon-prefixed rows, decorative toggles, and value displays — proving the stack stays out of your way and lets the content speak.

Settings
General
About v17.4
Auto-correction
Caps lock
Language
English (United States)
Français
Deutsch
Español
日本語
Keyboard
Predictive text
Smart punctuation
Slide to type
Display
Dark mode
Auto-brightness
Bold text
Text size
Small
Default
Large
Extra large
Sounds
Ringer & alerts 75%
Change with buttons
Haptics
Notifications
Show previews When unlocked
Scheduled summary
Privacy
Location services
Analytics

A wizard, walking forward.

Steppers are just stacks where you only call push. Each step hits a "Continue" button to advance, or "Back" to retreat one level. The Confirm step calls reset() to return to the start when you submit.

Step 1 of 4

Create your account

Start with your name and email — we'll never share it.

Step 2 of 4

Tell us about you

A few details so we can tailor the experience.

Step 3 of 4

Pick your preferences

You can change these later.

Step 4 of 4

Looks good?

Tim Cook · hi@example.com
Frontend developer at Magic Spells
Subscribed to product updates and the weekly digest

Tune the feel of it.

Every part of the motion is exposed as a CSS custom property. Drag the sliders to change the slide distance, blur amount, horizontal scale, easing duration, and the opacity of inactive panels. Push and pop to feel the difference.

Playground
Section A
A child item
Another child
A — deep
Deepest item
Section B
Item one
Item two
Item three
Section C
Quiet content
Duration 420ms
Blur 2px
Scale X 1.00
Offset 100%
Inactive opacity 0.30
Transition
Presets

Listen in.

Every push, pop, and reset fires a bubbling, composed CustomEvent. Push events are cancelable — call e.preventDefault() in a panel-stack:push handler to abort navigation. Try clicking around the panel; the log on the right captures every event.

Events
One
Two
Leaf node.
Deep
Bottom of the well.
// waiting for events…
stack.addEventListener('panel-stack:push', (e) => { console.log(e.detail) // { fromHandle: 'evt-root', toHandle: 'evt-one' } }) // cancelable — preventDefault() aborts the push stack.addEventListener('panel-stack:push', (e) => { if (e.detail.toHandle === 'evt-deep') e.preventDefault(); })

The whole API, on one screen.

Two elements, three methods, three events, eight CSS custom properties. That's everything.

Elements & attributes

Element / attribute
On
Description
<panel-stack>
Root container managing the stack
initial
panel-stack
Handle of the panel to start on (default: first child)
<stack-panel>
One panel in the stack
handle
stack-panel
Identifier used by push() / pop()
data-action-stack-push
any descendant
Click trigger that calls push() with the target attribute
target
push trigger
Handle of the panel to push to
data-action-stack-pop
any descendant
Click trigger that calls pop()
data-stack-focus
any descendant
First focusable target when its panel becomes current

Methods & getters

Name
Returns
Description
push(handle)
boolean
Slide to the named panel · returns false if cancelled or unknown
pop()
boolean
Pop the top panel · false if at root
reset()
void
Collapse the stack to root
currentHandle
string
Read-only · top of the stack
currentPanel
StackPanel
Read-only · the visible <stack-panel> element
depth
number
Read-only · stack length (root = 1)

Events

Event
Cancelable
Detail
panel-stack:push
yes
{ fromHandle, toHandle }
panel-stack:pop
no
{ fromHandle, toHandle }
panel-stack:reset
{ rootHandle }

CSS custom properties

Property
Default
Description
--ps-transition-duration
420ms
Slide animation duration
--ps-transition-timing
cubic-bezier(0.16, 0.87, 0.64, 1)
Easing for the slide
--ps-blur-duration
280ms
Separate timing for the blur fade
--ps-perspective
1200px
Depth of the 3D scene

Per-state values

Each state (current, previous, next) has its own translate, scale, blur, and opacity custom property. Override the ones for a single state to break symmetry — e.g. make panels exit faster than they enter.

Property pattern
Defaults
Notes
--ps-translate-{state}
0% · calc(-100% - 50px) · calc(100% + 50px)
X-axis translate per state
--ps-scale-x-{state}
1 · 1.1 · 1.1
Horizontal scale per state (values > 1 stretch, < 1 shrink, negative flips)
--ps-blur-{state}
0px · 2px · 2px
Filter blur per state
--ps-opacity-{state}
1 · 0.3 · 0.3
Opacity per state

Browser support

Modern browsers with custom elements + the inert attribute. Works in Chrome 102+, Firefox 112+, Safari 15.5+, Edge 102+. Honors prefers-reduced-motion: reduce by suppressing the scale, blur, and transition.