blaze-motion
Motion presets
Spread onto your own elements. No wrappers, no extra DOM nodes.
Copy-paste through a shadcn registry. The files land in your repo and you import them from your own paths.
Setup
Requires React 19 — the provider uses two React 19-only constructs (context-as-provider, ref as a plain prop) and breaks on React 18.
Add the engine
npx shadcn@latest add https://motion.asmitsah.dev/r/motion.jsonMount the provider once, at the root
import type { ReactNode } from "react"; import { MotionProvider } from "@/lib/motion"; export default function RootLayout({ children }: { children: ReactNode }) { return ( <html lang="en"> <body> <MotionProvider>{children}</MotionProvider> </body> </html> ); }A lazily-loaded feature bundle behind LazyMotion strict, with reducedMotion="user" set. Under that preference motion cuts transform and positional properties — x, y, scale — straight to their end value and animates the rest. Each preset table names which is which.
Usage
import { Section } from "@/lib/motion";
import { fadeUp } from "@/lib/motion/presets/fade-up";
export function Pricing() {
return (
<Section {...fadeUp} className="grid gap-6 md:grid-cols-3">
<PlanCard plan="Starter" />
<PlanCard plan="Team" />
<PlanCard plan="Scale" />
</Section>
);
}A preset is a plain object and the barrel carries no "use client", so a Server Component can import one as data.
Customisation
Globally — config.ts
Every tunable number lives here. No literal value appears anywhere else; every preset derives from this file.
// lib/motion/config.ts — yours. An update never overwrites it.
import type { ViewportOptions } from "motion/react";
type CubicBezier = [number, number, number, number];
export const duration = {
fast: 0.35,
base: 0.55,
} as const;
export const travel = { sm: 16, md: 28 } as const;
export const staggerStep = 0.08;
export const ease: CubicBezier = [0.16, 1, 0.3, 1];
export const fade = { from: 0, to: 1 } as const;
export const zoom = { in: 0.94, out: 1.06 } as const;
export const blur = { from: 8, to: 0 } as const;
export const rest = { x: 0, y: 0, scale: 1 } as const;
// `amount` must be "some", never a number — a numeric threshold leaves a taller-than-viewport
// element stuck at its initial state forever.
export const viewport: ViewportOptions = {
once: true,
amount: "some",
margin: "0px 0px -12% 0px",
};Once — after the spread
import { Section } from "@/lib/motion";
import { fadeUp } from "@/lib/motion/presets/fade-up";
export function Hero() {
return (
<Section {...fadeUp} transition={{ duration: 1.2, ease: "easeOut" }}>
<h2>Slower, just here</h2>
</Section>
);
}In sequence — withDelay
Returns a copy of the preset with a delay on its transition. It sequences siblings that are not a list — a stagger needs one parent owning the variants and children that inherit them.
import { H1, P, staggerStep, withDelay } from "@/lib/motion";
import { slideUp } from "@/lib/motion/presets/slide-up";
export function Hero() {
return (
<section>
<P {...slideUp}>blaze-motion</P>
<H1 {...withDelay(slideUp, staggerStep)}>
Your markup does not change shape.
</H1>
<P {...withDelay(slideUp, staggerStep * 2)}>
Spread onto the elements you already wrote.
</P>
</section>
);
}