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.
| 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
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.
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