🍡 mochi

SSR framework for Svelte 5 + Bun with islands-based selective hydration

On this page

View Transitions

<ViewTransitions /> opts your app into the browser’s cross-document View Transitions API, animating full-page navigations with zero client JavaScript. Mochi is an MPA, so every navigation is a real page load. The browser does the work. You declare the animation.

Render it from a component that appears on every page — for example, a shared page shell. Both the page you leave and the page you land on must opt in.

<script>
  import { ViewTransitions } from 'mochi-framework/components';
</script>

<ViewTransitions type="fade" />

Navigate between pages and they crossfade. That is the whole setup.

Props

PropTypeDefaultDescription
type'fade' \| 'slide' \| 'scale' \| 'blur' \| 'flip''fade'The transition preset.
custom{ out?: string; in?: string }Custom keyframe bodies. Overrides type.
durationnumber (ms)250Animation duration.
easingstring'ease'The animation timing function.
regionsstring \| string[]Confine the animation to elements with these view-transition-names.
keepElementSelectorsstring \| string[]CSS selectors for persistent chrome to hold still across navigations.
<ViewTransitions type="slide" duration={400} easing="cubic-bezier(0.22, 1, 0.36, 1)" />

Five presets ship built in: fade, slide, scale, blur, and flip. They animate the page root, so they apply to any page with no per-element setup, and reduced-motion users get no animation automatically.

Custom transitions

Pass custom to bring your own animation. out and in are the body of each keyframe for the page you leave and the page you land on. Mochi wraps each into an @keyframes for you.

<ViewTransitions
  custom={{
    out: 'to { opacity: 0; transform: rotate(8deg) }',
    in: 'from { opacity: 0; transform: rotate(-8deg) }',
  }}
  easing="cubic-bezier(0.22, 1, 0.36, 1)"
/>

Either side is optional. custom composes with duration, easing, regions, keepElementSelectors, and reduced-motion.

Animating only part of the page

The API always snapshots the whole viewport. You can scope which parts animate. Pass regions to confine the transition to elements you gave a view-transition-name. Everything else swaps instantly.

<ViewTransitions type="slide" regions="card" />

<section style="view-transition-name: card"></section>

An empty array (regions={[]}) disables the animation entirely.

Keeping elements still

To hold persistent chrome — a banner, sidebar, or header — still while the rest of the page transitions, pass keepElementSelectors a list of CSS selectors. Mochi assigns each selector a unique view-transition-name and emits the freeze CSS. Render the same list on every page.

<ViewTransitions type="fade" keepElementSelectors={['.banner', '.sidebar']} />

See it in action

Live demos showing key concepts from this page