Breakpoints

Configure responsive breakpoints and per-breakpoint animation behavior.

Motion.page uses four named breakpoint ranges — phones, tablets, laptops, and desktop — with configurable pixel thresholds. In the SDK, breakpoints are enforced with native matchMedia. In the Builder, a range selector wraps the generated code in a matchMedia guard automatically.


Default Breakpoint Values

These are the global defaults. All SDK examples on this page use them. You can change the thresholds in Builder > Settings > Breakpoints.

RangeConditionDefault threshold
Phonesmax-width576px
Tabletsmax-width768px
Laptopsmax-width992px
Desktopmin-width993px

Each threshold is a maximum width for the named range. Desktop has no upper bound — it applies from 993px upward.


SDK: matchMedia Guard

The SDK has no built-in breakpoint system. Use window.matchMedia() to conditionally register animations. The check runs once at script execution time — if the condition doesn’t match, the animation is never registered.

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

// Phones only — up to 576px
if (matchMedia("screen and (max-width: 576px)").matches) {
  Motion("mobile-hero", ".hero", {
    from: { opacity: 0, y: 20 },
    duration: 0.5,
  }).onPageLoad();
}

Per-Range Examples

Phones only

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

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

Tablets only

Tablets span from just above the phones threshold to the tablets threshold:

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

if (matchMedia("screen and (min-width: 577px) and (max-width: 768px)").matches) {
  Motion("tablet-grid", ".card", {
    from: { opacity: 0, scale: 0.95 },
    duration: 0.4,
    stagger: 0.06,
    ease: "power2.out",
  }).onScroll({ toggleActions: "play none none none" });
}

Laptops only

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

if (matchMedia("screen and (min-width: 769px) and (max-width: 992px)").matches) {
  Motion("laptop-parallax", ".bg-shape", {
    from: { y: -40 },
    to: { y: 40 },
    duration: 1,
  }).onScroll({ scrub: true });
}

Desktop only

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

// Skip on mobile and tablet — hover effects don't suit 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" });
}

Phones and tablets (up to 768px)

Combine ranges by using the upper threshold of the outermost range:

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

if (matchMedia("screen and (max-width: 768px)").matches) {
  Motion("small-screen-fade", ".hero-image", {
    from: { opacity: 0 },
    duration: 0.6,
  }).onPageLoad();
}

Tablets, laptops, and desktop (min-width 577px)

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

if (matchMedia("screen and (min-width: 577px)").matches) {
  Motion("wide-parallax", ".feature-block", {
    from: { x: -60, opacity: 0 },
    duration: 0.8,
    stagger: 0.1,
    ease: "power3.out",
  }).onScroll({ toggleActions: "play none none none" });
}

Different Animations per Breakpoint

Use multiple matchMedia checks to run different animations at different sizes. Only one block runs per page load.

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

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

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

// Phones: instant fade on load — skip scroll complexity
if (matchMedia("screen and (max-width: 576px)").matches) {
  Motion("hero-effect", ".hero-image", {
    from: { opacity: 0 },
    duration: 0.5,
  }).onPageLoad();
}

Builder: Enabled Breakpoints

In the Builder, each timeline has an Enabled Breakpoints range selector in the Trigger Section. It maps to a five-point scale:

PointIconRange
1⊘ (disabled)Below phones — animation off
2PhonePhones (max-width: 576px)
3TabletTablets (max-width: 768px)
4LaptopLaptops (max-width: 992px)
5Desktop and above (no upper bound)

The default range is 1 → 5 (all devices). Drag either handle inward to restrict the animation to a narrower range.

The Builder wraps the generated code automatically:

typescript
// Range set to phones + tablets [1–3] generates:
if (matchMedia("screen and (max-width: 768px)").matches) {
  // your animation code
}

// Range set to desktop only [4–5] generates:
if (matchMedia("screen and (min-width: 992px)").matches) {
  // your animation code
}

No manual code is needed when using the Builder — the matchMedia guard is injected at export time.


Custom Breakpoint Thresholds

The pixel values for phones, tablets, and laptops are global settings. Change them in Builder > Settings > Breakpoints. All timelines and generated SDK code will reflect the updated thresholds.

SettingTooltipDefault
PhonesMaximum width in pixels576
TabletsMaximum width in pixels768
LaptopsMaximum width in pixels992

After updating these values, the breakpoint reference table above changes accordingly. Update your SDK matchMedia calls to match if you override the defaults.

custom-breakpoints.ts
import { Motion } from "@motion.page/sdk";

// Site uses custom breakpoints: phones 480px, tablets 900px, desktop 901px+
if (matchMedia("screen and (min-width: 901px)").matches) {
Motion("desktop-reveal", ".feature", {
  from: { opacity: 0, y: 40 },
  duration: 0.7,
  stagger: 0.08,
  ease: "power2.out",
}).onScroll({ toggleActions: "play none none none" });
}

Resize-Aware Breakpoints

matchMedia checks run once at load. If a user resizes their browser past a threshold after the page has loaded, registered animations don’t update. For resize-aware behavior, use Motion.context() with a MediaQueryList change listener:

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

const mq = matchMedia("screen and (min-width: 993px)");

let ctx = Motion.context(() => {
  if (mq.matches) {
    Motion("desktop-scroll", ".card", {
      from: { opacity: 0, y: 40 },
      duration: 0.6,
      stagger: 0.1,
    }).onScroll({ toggleActions: "play none none none" });
  }
});

mq.addEventListener("change", () => {
  ctx.revert();
  ctx = Motion.context(() => {
    if (mq.matches) {
      Motion("desktop-scroll", ".card", {
        from: { opacity: 0, y: 40 },
        duration: 0.6,
        stagger: 0.1,
      }).onScroll({ toggleActions: "play none none none" });
    }
  });
});

ctx.revert() kills all timelines in the context and restores initial CSS. Re-running Motion.context() reinitializes them with fresh selectors. For most sites, a one-time check at load is sufficient — resize-aware handling is only needed when your layout shifts significantly on breakpoint crossing.