Responsive Animations

Create animations that adapt to different screen sizes with breakpoints and matchMedia.

The SDK has no built-in breakpoint system — responsive animations are handled with native window.matchMedia(). Wrap animation initialization in a media query check, and the animation only runs when the condition matches.

Mobile-Only Animation

Use matchMedia to restrict an animation to a specific screen size:

typescript
import { Motion } from "@motion.page/sdk";

if (matchMedia("screen and (max-width: 576px)").matches) {
  Motion("mobile-reveal", ".hero", {
    from: { opacity: 0, y: 30 },
    duration: 0.6,
    ease: "power2.out",
  }).onPageLoad();
}

The check runs once at script execution time. If the condition doesn’t match, the animation is never registered — no overhead, no cleanup needed.


Different Animations per Screen Size

Combine multiple matchMedia checks to run different animations at different breakpoints:

typescript
import { Motion } from "@motion.page/sdk";

// Desktop: parallax scroll effect
if (matchMedia("screen and (min-width: 993px)").matches) {
  Motion("hero-parallax", ".hero-image", {
    from: { y: -60 },
    to: { y: 60 },
    duration: 1,
  }).onScroll({ scrub: true });
}

// Tablet: simpler fade-in on scroll
if (
  matchMedia(
    "screen and (min-width: 577px) and (max-width: 992px)"
  ).matches
) {
  Motion("hero-fade", ".hero-image", {
    from: { opacity: 0 },
    duration: 0.8,
    ease: "power2.out",
  }).onScroll({ toggleActions: "play none none none" });
}

// Mobile: no scroll animation — just fade in on load
if (matchMedia("screen and (max-width: 576px)").matches) {
  Motion("hero-mobile", ".hero-image", {
    from: { opacity: 0 },
    duration: 0.5,
  }).onPageLoad();
}

Default Breakpoint Values

Motion.page uses these default breakpoints (configurable in Builder > Settings > Breakpoints):

DeviceBreakpoint
Phonesmax-width: 576px
Tabletsmax-width: 768px
Laptopsmax-width: 992px
Desktopmin-width: 993px

Use these values for consistency across SDK code and the Builder.


Disable an Animation on Mobile

To run an animation only on desktop, negate the mobile condition:

typescript
import { Motion } from "@motion.page/sdk";

// Skip on mobile — hover effects don't make sense on touch screens
if (matchMedia("screen and (min-width: 993px)").matches) {
  Motion("card-hover", ".card", {
    to: { y: -8, boxShadow: "0 12px 24px rgba(0,0,0,0.15)" },
    duration: 0.3,
    ease: "power2.out",
  }).onHover({ each: true, onLeave: "reverse" });
}

Handling Resize with Motion.context()

matchMedia checks run once. If the user resizes their browser window after load, the animation won’t update. For resize-aware animations, use Motion.context() with a matchMedia event listener:

typescript
import { Motion } from "@motion.page/sdk";

let ctx = Motion.context(() => {
  if (matchMedia("screen and (min-width: 993px)").matches) {
    Motion("desktop-anim", ".card", {
      from: { opacity: 0, y: 40 },
      duration: 0.6,
      stagger: 0.1,
    }).onScroll({ toggleActions: "play none none none" });
  }
});

// Reinitialize on breakpoint crossing
const mq = matchMedia("screen and (min-width: 993px)");
mq.addEventListener("change", () => {
  ctx.revert();
  ctx = Motion.context(() => {
    if (mq.matches) {
      Motion("desktop-anim", ".card", {
        from: { opacity: 0, y: 40 },
        duration: 0.6,
        stagger: 0.1,
      }).onScroll({ toggleActions: "play none none none" });
    }
  });
});

ctx.revert() kills all timelines created inside the context and restores initial CSS. Re-running Motion.context() reinitializes them fresh.

For most production sites, breakpoint crossings are rare — a simple one-time matchMedia check on load is sufficient and has less overhead.


Reduced Motion

Respect the user’s system preference with prefers-reduced-motion:

typescript
import { Motion } from "@motion.page/sdk";

const prefersReduced = matchMedia(
  "(prefers-reduced-motion: reduce)"
).matches;

Motion("hero", ".hero-title", {
  from: prefersReduced
    ? { opacity: 0 }                  // Accessibility: fade only, no movement
    : { opacity: 0, y: 60 },
  duration: prefersReduced ? 0.3 : 0.9,
  ease: "power2.out",
}).onPageLoad();

Or skip non-essential animations entirely:

typescript
if (!matchMedia("(prefers-reduced-motion: reduce)").matches) {
  Motion("decorative-parallax", ".bg-shape", {
    from: { y: -80 },
    to: { y: 80 },
    duration: 1,
  }).onScroll({ scrub: true });
}

Builder Breakpoints

When using the Motion.page Builder, per-timeline breakpoints are configured in the Trigger Section > Enabled Breakpoints panel. The Builder wraps the generated animation code in a matchMedia check automatically — no manual code needed.

The breakpoint range selector maps to the same default values listed above. Adjust the global thresholds under Settings > Breakpoints.

See Breakpoints for the full Builder workflow.