Preset
stagger
A pair that lands a list one item at a time: staggerParent times the sequence, staggerChild plays each item. The parent animates nothing of its own — it names the hidden and visible variants its children inherit and the step between them. The child carries the opacity and y, and needs a parent above it.
Opacity + transform
- Copy, do not install
- One file you own
- No wrappers
- Every number in config.ts
Every row starts 16px low and invisible, and each one begins 0.08s after the row above it.
Usage
import { Li, Ul } from "@/lib/motion";
import { staggerChild } from "@/lib/motion/presets/stagger-child";
import { staggerParent } from "@/lib/motion/presets/stagger-parent";
export function Features() {
return (
<Ul {...staggerParent} className="grid gap-6 md:grid-cols-3">
{features.map((feature) => (
<Li key={feature.title} {...staggerChild}>
<FeatureCard title={feature.title} />
</Li>
))}
</Ul>
);
}Installation
npx shadcn@latest add https://motion.asmitsah.dev/r/stagger.jsonProps and tokens
What it sets
| Property | Initial | In view | Reduced motion | Reads from config.ts |
|---|---|---|---|---|
| opacity | 0 | 1 | Animates | fade.from → fade.to |
| y | 16 | 0 | Instant | travel.sm → rest.y |
Transition and viewport
| Option | Value | Reads from config.ts |
|---|---|---|
| staggerChildren | 0.08 | staggerStep |
| duration | 0.35 | duration.fast |
| ease | [0.16,1,0.3,1] | ease |
| viewport.once | true | viewport |
| viewport.amount | "some" | viewport |
| viewport.margin | "0px 0px -12% 0px" | viewport |
Every motion prop the preset does not set — whileHover, whileTap, whileFocus — stays available at the call site. Write it after the spread and it wins.
layout and drag need the domMax feature bundle, not domAnimation — swap the lazy import in provider.tsx. It adds roughly 13kb gzipped on top of domAnimation.