Skip to content

Transitions

Transitions animate the change from one slide to the next. astro-slides ships built-in slide transitions (fade and directional slides) plus <Morph> for object continuity — an element that appears to move and reshape as it carries across a slide boundary.

There is no animation library. Slide transitions are pure CSS keyed off the deck’s past / present / future state machine, and morphs use the browser’s native View Transitions API with a hand-rolled FLIP fallback where it isn’t supported (ADR-0006).

Set a default transition for the whole deck in the headmatter (the first frontmatter block). It applies to every slide unless a slide overrides it.

---
title: My Deck
theme: starter
transition: fade
---

The default is fade if you set nothing.

Any slide can override the deck default with its own transition: frontmatter. The transition describes how that slide enters.

---
transition: slide-left
---
## Directional transition
This slide arrives with a slide-left transition.

transition: accepts these string presets:

PresetEffect
fadeCross-fade (opacity only). The default.
fade-outOpacity fade variant.
view-transitionOpacity swap that opts into the View Transitions root cross-fade at runtime.
slide-leftIncoming slide moves in from the right; outgoing exits left.
slide-rightIncoming slide moves in from the left; outgoing exits right.
slide-upIncoming slide moves up from below; outgoing exits upward.
slide-downIncoming slide moves down from above; outgoing exits downward.
noneNo transition — the slide snaps in.

Instead of a bare preset name you can pass an object to tune duration and easing per transition. name is required; duration (milliseconds) and easing (any CSS timing function) are optional.

---
transition:
name: slide-left
duration: 500
easing: cubic-bezier(0.4, 0, 0.2, 1)
---

When omitted, duration and easing fall back to the theme’s --slide-transition-duration and --slide-transition-easing custom properties.

<Morph> — object continuity across slides

Section titled “<Morph> — object continuity across slides”

<Morph> marks an element as the “same” object on two consecutive slides. Give the paired elements a matching id, and the runtime animates the element from its old position and size to its new ones as you advance — even if the tag, layout, or styling differs.

---
## Morph: before
<Morph id="hero" as="div" class="demo-hero">astro-slides</Morph>
---
layout: center
---
<Morph id="hero" as="div" class="demo-hero demo-hero--big">astro-slides</Morph>
The element above continues from the previous slide — it grows and re-centers in place
rather than fading out and back in.

Props:

  • id (required) — the pairing key. Elements with the same id on adjacent slides are matched.
  • as — the HTML tag to render. Defaults to span.
  • class — extra classes to apply to the rendered element.

<Morph> emits only a data-morph attribute; the runtime assigns a view-transition-name on the fly for the matched pair (both slides share one DOM, so a static shared name would collide) and clears it when the transition finishes.

  • View Transitions API (primary): the browser snapshots the old and new element and interpolates the whole subtree — position, size, and also color, opacity, and text reflow. GPU-composited, zero extra JS. Supported in Chromium, Safari 18+, and Firefox as it lands.
  • FLIP fallback: where the View Transitions API is unavailable, the runtime measures the element’s old and new boxes and animates the transform (translate + scale) with the Web Animations API. Position and size morph; color and opacity changes snap instead of interpolating.

Both paths reach the same final frame — the fallback is a graceful degradation.

A morph interpolates a paired element’s box — its position and size — and cross-fades its contents. The difference between a morph that reads as a smooth animation and one that looks like a dissolve is whether you give the eye movement to track:

  • Move or resize the element between slides. If a pair sits in the same place at the same size and only its contents change, there is no box to interpolate and you get a plain cross-fade — a dissolve. Reposition it, scale it, or both, and the browser glides it.
  • Keep the two ends visually similar. When the paired elements look alike, the content cross-fade is imperceptible and you read pure motion. Boxes stay boxes.
  • Morph many small objects at once. Give each of a set of elements its own stable id and rearrange them into a new formation on the next slide. Every object keeps its identity and flies to its new spot — the “magic move” effect. The morph-reel example does exactly this with twelve dots that never leave the stage.

You can make a group of morphs settle as a wave by giving each pair a different animation-delay. While a morph runs, the runtime flags <html data-as-morphing="vt"> and names each matched pair as-morph-0, as-morph-1, … in document order, so you can target their View Transition groups from a <style> block in your deck:

html[data-as-morphing="vt"]::view-transition-group(as-morph-1) { animation-delay: 40ms; }
html[data-as-morphing="vt"]::view-transition-group(as-morph-2) { animation-delay: 80ms; }

The morph itself uses an eased ~440ms timing by default, matching the FLIP fallback so both paths feel the same.

prefers-reduced-motion is respected. When a viewer has reduced motion enabled, slide transitions are disabled (the change applies instantly) and morphs skip their animation, snapping to the final layout. This is handled in CSS and short-circuited in the runtime, so no motion plays for viewers who opt out.

  • packages/client/components/Morph.astro
  • packages/client/src/styles/transitions.css
  • packages/client/src/styles/deck.css
  • packages/client/src/transitions/index.ts
  • packages/client/src/transitions/flip.ts
  • packages/client/src/transitions/detect.ts
  • packages/types/src/frontmatter.ts
  • examples/morph-reel/ — the object-continuity showcase deck
  • docs/decisions/0006-view-transitions-with-flip-fallback.md
  • docs/built/07-transitions.md
  • examples/minimal/slides.md