One overlay, three layouts. A record panel that morphs between slideover, modal and fullscreen with the View Transitions API — the entire component tree swaps, and the browser animates the difference.
This is a technique demo, not a package. The interesting part is the CSS:
src/styles/morph.css contains every recipe, with the
reasoning next to each rule.
- Crisp text while the panel resizes. View-transition snapshots are bitmaps; by default the browser stretches the old and new images into the interpolating box and cross-fades them, so text warps and smears. The fix: natural-size snapshots, the group box as a clipping reveal window, and no cross-fade on content-identical pairs. The precondition is that the content column has the same width in every layout. Use the break the fix toggle in the demo to compare against the default.
- Nested groups.
view-transition-group: contain+::view-transition-group-children(Chrome 140+) make the panel clip its named descendants instead of leaking them into the root transition. Older engines ignore the rules and still morph. - Typed, per-direction retiming. Every switch passes a type
(
om-slideover-to-modal, …), so:active-view-transition-type()can retime one morph family without touching any other view transition on the page — morphs into fullscreen glide on a decelerate, everything else springs, with a settle-pulse keyframe supplying the bounce. - The snapshot lock. While a morph runs, an
om-vt-lockclass on<html>force-disables the overlay shells' own CSS transitions — otherwise the "new" snapshot captures a panel mid-animation. - Staged aside. The activity column is rendered just beyond the slideover's edge with real geometry, so morphing to the split layouts reveals it instead of fading in a snapshot that was never captured.
- Docked closes without
<Transition>. Vue's rAF-gated leave keeps the old panel in the DOM while snapshots are taken; a duplicateview-transition-namesilently aborts the whole morph. The docked panel collapses via a raw CSS width transition instead.
Falls back gracefully: without document.startViewTransition (or with
prefers-reduced-motion) every switch is an instant swap.
pnpm install
pnpm dev