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. Use Motion.responsive() when variants should swap live, or native matchMedia for a one-time conditional guard. The Builder generates the appropriate responsive code 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.

Range Condition Default threshold
Phones max-width 576px
Tablets max-width 768px
Laptops max-width 992px
Desktop min-width 993px

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


SDK: matchMedia Guard

Use window.matchMedia() when a single animation only needs to be registered at one size. The check runs once at script execution time — if the condition doesn’t match, the animation is never registered. For variants that react to resize, use Motion.responsive().

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:

Point Icon Range
1 ⊘ (disabled) Below phones — animation off
2 Phone Phones (max-width: 576px)
3 Tablet Tablets (max-width: 768px)
4 Laptop Laptops (max-width: 992px)
5 Desktop 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.

Setting Tooltip Default
Phones Maximum width in pixels 576
Tablets Maximum width in pixels 768
Laptops Maximum width in pixels 992

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.