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().
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
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:
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
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
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:
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)
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.
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:
// 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.
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:
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.
Related
- Responsive Animations — matchMedia patterns, reduced motion, and SPA-aware reinit
- Core Concepts —
Motion.context()for lifecycle management and teardown - Page Load Trigger — entrance animations on load
- Scroll Trigger — viewport-based scroll animations