Animated components

Components that already know how to move.

Twelve of them, built from the same four springs, so they look right together. The code next to each one is generated from the animation itself, and every one opens in the editor so you can take it apart.

CSS and Motion handle the parts inside a component differently. CSS can reach them with selectors, so you paste the export and add one class. Motion can only animate a part that’s a motion.div, so the export writes those into your component’s markup for you. Still one paste.

Overlay

5

Dialog

on open

Delete this animation?

This removes the document and the component source stored with it. It cannot be undone.

Cancel Delete
settles ~860 ms
2 animated · complete, in your markup
import { motion } from "motion/react";

export function Dialog() {
  return (
    <div className="relative flex h-[260px] w-full items-center justify-center overflow-hidden rounded-lg bg-muted/40">
      <motion.div
        className="absolute inset-0 bg-background/70"
        initial={{ opacity: 0 }}
        animate={{ opacity: [0, 1] }}
        transition={{
          opacity: { duration: 0.26, times: [0, 1], ease: "easeOut" },
        }}
      ></motion.div>
      <motion.div
        className="relative w-[320px] rounded-xl border border-border bg-card p-5 shadow-xl"
        initial={{ opacity: 0, y: 10, scale: 0.96 }}
        animate={{ opacity: [0, 1], y: 0, scale: 1 }}
        transition={{
          opacity: { duration: 0.22, times: [0, 1], ease: "easeOut" },
          y: { type: "spring", stiffness: 260, damping: 24, mass: 1, restDelta: 0.01, restSpeed: 0.01 },
          scale: { type: "spring", stiffness: 260, damping: 24, mass: 1 },
        }}
      >
        <h2 className="text-[15px] font-semibold text-foreground">Delete this animation?</h2>
        <p className="mt-2 text-[13px] leading-relaxed text-muted-foreground">This removes the document and the component source stored with it. It cannot be undone.</p>
        <div className="mt-5 flex justify-end gap-2">
          <span className="rounded-md border border-border px-3 py-1.5 text-[13px] text-foreground">Cancel</span>
          <span className="rounded-md bg-primary px-3 py-1.5 text-[13px] font-medium text-primary-foreground">Delete</span>
        </div>
      </motion.div>
    </div>
  );
}

The backdrop dims while the panel rises and scales up. Two layers moving together is what makes a dialog feel polished.

Open in editor

Dropdown

on open
Options
Duplicate
Rename
Export code
Delete
settles ~840 ms
1 animated · complete, in your markup
import { motion } from "motion/react";

export function Dropdown() {
  return (
    <div className="flex h-[260px] w-full justify-center rounded-lg bg-muted/40 pt-6">
      <div className="flex w-[220px] flex-col items-stretch gap-2">
        <span className="rounded-md border border-border bg-card px-3 py-1.5 text-center text-[13px] text-foreground">Options</span>
        <motion.div
          className="rounded-lg border border-border bg-popover p-1 shadow-lg"
          initial={{ opacity: 0, y: -6, scale: 0.97 }}
          animate={{ opacity: [0, 1], y: 0, scale: 1 }}
          transition={{
            opacity: { duration: 0.22, times: [0, 1], ease: "easeOut" },
            y: { type: "spring", stiffness: 260, damping: 24, mass: 1, restDelta: 0.01, restSpeed: 0.01 },
            scale: { type: "spring", stiffness: 260, damping: 24, mass: 1 },
          }}
        >
          <div className="rounded px-2.5 py-1.5 text-[13px] text-foreground">Duplicate</div>
          <div className="rounded px-2.5 py-1.5 text-[13px] text-foreground">Rename</div>
          <div className="rounded px-2.5 py-1.5 text-[13px] text-foreground">Export code</div>
          <div className="mt-1 border-t border-border pt-1">
            <div className="rounded px-2.5 py-1.5 text-[13px] text-destructive">Delete</div>
          </div>
        </motion.div>
      </div>
    </div>
  );
}

Grows out of the trigger with a small scale and a short rise, quick enough to feel like a reveal.

Open in editor

Sheet

on open
Inspector
settles ~1140 ms
2 animated · complete, in your markup
import { motion } from "motion/react";

export function Sheet() {
  return (
    <div className="relative flex h-[260px] w-full justify-end overflow-hidden rounded-lg bg-muted/40">
      <motion.div
        className="absolute inset-0 bg-background/70"
        initial={{ opacity: 0 }}
        animate={{ opacity: [0, 1] }}
        transition={{
          opacity: { duration: 0.32, times: [0, 1], ease: "easeOut" },
        }}
      ></motion.div>
      <motion.div
        className="relative h-full w-[240px] border-l border-border bg-card p-5"
        initial={{ opacity: 0, x: 240 }}
        animate={{ opacity: [0, 1], x: 0 }}
        transition={{
          opacity: { duration: 0.22, times: [0, 1], ease: "easeOut" },
          x: { type: "spring", stiffness: 260, damping: 24, mass: 1 },
        }}
      >
        <span className="text-[10px] uppercase tracking-[0.14em] text-muted-foreground">Inspector</span>
        <div className="mt-4 flex flex-col gap-3">
          <div className="h-8 rounded-md border border-border"></div>
          <div className="h-8 rounded-md border border-border"></div>
          <div className="h-8 rounded-md border border-border"></div>
        </div>
      </motion.div>
    </div>
  );
}

A side panel that travels its own width, and takes its time doing it — distance and duration have to agree or it reads as teleporting.

Open in editor

Popover

on open
Stagger step

An even step is what collapses into staggerChildren on export.

60ms
settles ~850 ms
1 animated · complete, in your markup
import { motion } from "motion/react";

export function Popover() {
  return (
    <div className="flex h-[260px] w-full flex-col items-center justify-center gap-2 rounded-lg bg-muted/40">
      <motion.div
        className="w-[240px] rounded-lg border border-border bg-popover p-3.5 shadow-lg"
        initial={{ opacity: 0, y: 8, scale: 0.96 }}
        animate={{ opacity: [0, 1], y: 0, scale: 1 }}
        transition={{
          opacity: { duration: 0.22, times: [0, 1], ease: "easeOut" },
          y: { type: "spring", stiffness: 260, damping: 24, mass: 1, restDelta: 0.01, restSpeed: 0.01 },
          scale: { type: "spring", stiffness: 260, damping: 24, mass: 1 },
        }}
      >
        <span className="block text-[12px] font-medium text-foreground">Stagger step</span>
        <p className="mt-1 text-[12px] leading-relaxed text-muted-foreground">An even step is what collapses into staggerChildren on export.</p>
      </motion.div>
      <span className="rounded-md border border-border bg-card px-3 py-1.5 text-[13px] text-foreground">60ms</span>
    </div>
  );
}

The same motion as the dropdown, pointed the other way. One decision, used twice.

Open in editor

Tooltip

on hover
Jump to start Home
settles ~460 ms
1 animated · complete, in your markup
import { motion } from "motion/react";

export function Tooltip() {
  return (
    <div className="flex h-[260px] w-full flex-col items-center justify-center gap-2 rounded-lg bg-muted/40">
      <motion.span
        className="rounded-md bg-popover px-2.5 py-1 text-[12px] text-popover-foreground shadow-md"
        initial={{ opacity: 0, y: 4, scale: 0.96 }}
        animate={{ opacity: [0, 1], y: 0, scale: 1 }}
        transition={{
          opacity: { duration: 0.12, times: [0, 1], ease: "easeOut" },
          y: { type: "spring", stiffness: 480, damping: 34, mass: 1 },
          scale: { type: "spring", stiffness: 480, damping: 34, mass: 1 },
        }}
      >Jump to start</motion.span>
      <span className="rounded-md border border-border bg-card px-3 py-1.5 text-[13px] text-foreground">Home</span>
    </div>
  );
}

Four pixels of rise and a hair of scale, over before you could look away. You already know what you pointed at, so the motion's only job is to not make you wait for it.

Open in editor

Disclosure

2

Accordion

on expand
What is a spring?

Stiffness is how hard it pulls, damping is how fast it settles. Neither is a duration, which is why they are hard to type and easy to drag.

settles ~910 ms
2 animated · complete, in your markup
import { motion } from "motion/react";

export function Accordion() {
  return (
    <div className="flex h-[260px] w-full items-start justify-center rounded-lg bg-muted/40 p-6">
      <div className="w-[300px] rounded-lg border border-border bg-card">
        <div className="flex items-center justify-between px-4 py-3">
          <span className="text-[13px] font-medium text-foreground">What is a spring?</span>
          <motion.svg
            className="size-3.5 shrink-0 text-muted-foreground"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            stroke-linecap="round"
            stroke-linejoin="round"
            initial={{ rotate: 0 }}
            animate={{ rotate: 45 }}
            transition={{
              rotate: { type: "spring", stiffness: 260, damping: 24, mass: 1, restDelta: 0.01, restSpeed: 0.01 },
            }}
          ><path d="M5 12h14"></path><path d="M12 5v14"></path></motion.svg>
        </div>
        <motion.div
          className="overflow-hidden border-t border-border px-4 py-3"
          initial={{ opacity: 0, y: -8 }}
          animate={{ opacity: [0, 1], y: 0 }}
          transition={{
            opacity: { duration: 0.22, times: [0, 1], ease: "easeOut", delay: 0.04 },
            y: { type: "spring", stiffness: 260, damping: 24, mass: 1, restDelta: 0.01, restSpeed: 0.01, delay: 0.04 },
          }}
        >
          <p className="text-[12px] leading-relaxed text-muted-foreground">Stiffness is how hard it pulls, damping is how fast it settles. Neither is a duration, which is why they are hard to type and easy to drag.</p>
        </motion.div>
      </div>
    </div>
  );
}

The marker turns while the panel drops in. Animating height needs a measurement the export can't take, so a short rise and a fade do the job instead.

Open in editor

Collapsible

on expand
Advanced

Mass, rest delta and the settle threshold. Everything you can leave alone until you cannot.

settles ~1080 ms
2 animated · complete, in your markup
import { motion } from "motion/react";

export function Collapsible() {
  return (
    <div className="flex h-[260px] w-full items-start justify-center rounded-lg bg-muted/40 p-6">
      <div className="w-[280px]">
        <div className="flex items-center gap-2">
          <motion.svg
            className="size-3.5 shrink-0 text-muted-foreground"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            stroke-linecap="round"
            stroke-linejoin="round"
            initial={{ rotate: 0 }}
            animate={{ rotate: 90 }}
            transition={{
              rotate: { type: "spring", stiffness: 260, damping: 24, mass: 1 },
            }}
          ><path d="m9 18 6-6-6-6"></path></motion.svg>
          <span className="text-[13px] font-medium text-foreground">Advanced</span>
        </div>
        <motion.div
          className="mt-2 rounded-lg border border-border bg-card p-3.5"
          initial={{ opacity: 0, y: -10 }}
          animate={{ opacity: [0, 1], y: 0 }}
          transition={{
            opacity: { duration: 0.22, times: [0, 1], ease: "easeOut", delay: 0.04 },
            y: { type: "spring", stiffness: 260, damping: 24, mass: 1, restDelta: 0.01, restSpeed: 0.01, delay: 0.04 },
          }}
        >
          <p className="text-[12px] leading-relaxed text-muted-foreground">Mass, rest delta and the settle threshold. Everything you can leave alone until you cannot.</p>
        </motion.div>
      </div>
    </div>
  );
}

The chevron turns a quarter and the content lifts in behind it. The accordion's motion, at a smaller scale.

Open in editor

Navigation

2

Tabs

on select
Motion CSS GSAP

A native spring transition, sixteen lines, no keyframe array.

settles ~910 ms
2 animated · complete, in your markup
import { motion } from "motion/react";

export function Tabs() {
  return (
    <div className="flex h-[260px] w-full items-start justify-center rounded-lg bg-muted/40 p-6">
      <div className="w-[300px]">
        <div className="relative flex gap-1 rounded-lg border border-border bg-card p-1">
          <motion.span
            className="absolute inset-y-1 left-1 w-[96px] rounded-md bg-secondary"
            initial={{ x: 100 }}
            animate={{ x: 0 }}
            transition={{
              x: { type: "spring", stiffness: 480, damping: 34, mass: 1 },
            }}
          ></motion.span>
          <span className="relative z-10 w-[96px] py-1 text-center text-[12px] text-foreground">Motion</span>
          <span className="relative z-10 w-[96px] py-1 text-center text-[12px] text-muted-foreground">CSS</span>
          <span className="relative z-10 w-[96px] py-1 text-center text-[12px] text-muted-foreground">GSAP</span>
        </div>
        <motion.div
          className="mt-3 rounded-lg border border-border bg-card p-4"
          initial={{ opacity: 0, y: 6 }}
          animate={{ opacity: [0, 1], y: 0 }}
          transition={{
            opacity: { duration: 0.22, times: [0, 1], ease: "easeOut", delay: 0.07 },
            y: { type: "spring", stiffness: 260, damping: 24, mass: 1, restDelta: 0.01, restSpeed: 0.01, delay: 0.07 },
          }}
        >
          <p className="text-[12px] leading-relaxed text-muted-foreground">A native spring transition, sixteen lines, no keyframe array.</p>
        </motion.div>
      </div>
    </div>
  );
}

The indicator slides and the panel crossfades a beat behind it. The order matters: the thing you pressed answers first, the content follows.

Open in editor

Command palette

on ⌘K
Type a command…
Jump to start
Add keyframe at playhead
Export code
settles ~1150 ms
4 animated · complete, in your markup
import { motion } from "motion/react";

export function CommandPalette() {
  return (
    <div className="flex h-[260px] w-full items-start justify-center rounded-lg bg-muted/40 pt-7">
      <motion.div
        className="w-[300px] overflow-hidden rounded-xl border border-border bg-popover shadow-xl"
        initial={{ opacity: 0, y: 12, scale: 0.97 }}
        animate={{ opacity: [0, 1], y: 0, scale: 1 }}
        transition={{
          opacity: { duration: 0.22, times: [0, 1], ease: "easeOut" },
          y: { type: "spring", stiffness: 260, damping: 24, mass: 1, restDelta: 0.01, restSpeed: 0.01 },
          scale: { type: "spring", stiffness: 260, damping: 24, mass: 1 },
        }}
      >
        <div className="border-b border-border px-3.5 py-2.5 text-[13px] text-muted-foreground">Type a command…</div>
        <div className="p-1.5">
          <motion.div
            className="rounded-md px-2.5 py-1.5 text-[12px] text-foreground"
            initial={{ opacity: 0, y: 8 }}
            animate={{ opacity: [0, 1], y: 0 }}
            transition={{
              opacity: { duration: 0.22, times: [0, 1], ease: "easeOut", delay: 0.18 },
              y: { type: "spring", stiffness: 260, damping: 24, mass: 1, restDelta: 0.01, restSpeed: 0.01, delay: 0.18 },
            }}
          >Jump to start</motion.div>
          <motion.div
            className="rounded-md px-2.5 py-1.5 text-[12px] text-foreground"
            initial={{ opacity: 0, y: 8 }}
            animate={{ opacity: [0, 1], y: 0 }}
            transition={{
              opacity: { duration: 0.22, times: [0, 1], ease: "easeOut", delay: 0.24 },
              y: { type: "spring", stiffness: 260, damping: 24, mass: 1, restDelta: 0.01, restSpeed: 0.01, delay: 0.24 },
            }}
          >Add keyframe at playhead</motion.div>
          <motion.div
            className="rounded-md px-2.5 py-1.5 text-[12px] text-foreground"
            initial={{ opacity: 0, y: 8 }}
            animate={{ opacity: [0, 1], y: 0 }}
            transition={{
              opacity: { duration: 0.22, times: [0, 1], ease: "easeOut", delay: 0.3 },
              y: { type: "spring", stiffness: 260, damping: 24, mass: 1, restDelta: 0.01, restSpeed: 0.01, delay: 0.3 },
            }}
          >Export code</motion.div>
        </div>
      </motion.div>
    </div>
  );
}

The panel arrives, then the rows follow 60ms apart. An even step like that exports as a single Motion stagger.

Open in editor

Feedback

3

Toast

on publish
Changes published 3 files · 340ms
settles ~1650 ms
1 animated · complete, in your markup
import { motion } from "motion/react";

export function Toast() {
  return (
    <div className="flex h-[260px] w-full items-end justify-end rounded-lg bg-muted/40 p-5">
      <motion.div
        className="flex w-[280px] items-start gap-3 rounded-lg border border-border bg-card p-3.5 shadow-lg"
        initial={{ opacity: 0, x: 28 }}
        animate={{ opacity: [0, 1], x: 0 }}
        transition={{
          opacity: { duration: 0.22, times: [0, 1], ease: "easeOut" },
          x: { type: "spring", stiffness: 220, damping: 12, mass: 1, restDelta: 0.01, restSpeed: 0.01 },
        }}
      >
        <span className="mt-1 size-2 shrink-0 rounded-full bg-primary"></span>
        <span className="min-w-0">
          <span className="block text-[13px] font-medium text-foreground">Changes published</span>
          <span className="mt-0.5 block text-[12px] text-muted-foreground">3 files · 340ms</span>
        </span>
      </motion.div>
    </div>
  );
}

Arrives from the right with a visible overshoot. A toast is one of the few places a little bounce feels confident instead of noisy.

Open in editor

Switch

on toggle
Reduced motion
settles ~420 ms
2 animated · complete, in your markup
import { motion } from "motion/react";

export function Switch() {
  return (
    <div className="flex h-[260px] w-full items-center justify-center gap-3 rounded-lg bg-muted/40">
      <motion.span
        className="relative flex h-6 w-11 items-center rounded-full bg-primary px-0.5"
        initial={{ opacity: 0.5 }}
        animate={{ opacity: [0.5, 1] }}
        transition={{
          opacity: { duration: 0.24, times: [0, 1], ease: "easeOut" },
        }}
      >
        <motion.span
          className="size-5 rounded-full bg-primary-foreground shadow"
          initial={{ x: -20, scale: 0.9 }}
          animate={{ x: 0, scale: 1 }}
          transition={{
            x: { type: "spring", stiffness: 480, damping: 34, mass: 1, restDelta: 0.01, restSpeed: 0.01 },
            scale: { type: "spring", stiffness: 480, damping: 34, mass: 1 },
          }}
        ></motion.span>
      </motion.span>
      <span className="text-[13px] text-foreground">Reduced motion</span>
    </div>
  );
}

The thumb snaps and the track catches up. Stiff enough to feel mechanical, because a switch is a physical metaphor and softness makes it feel broken.

Open in editor

Alert

on error
Export failed The spring is cut mid-overshoot, so Motion falls back to sampled keyframes.
settles ~870 ms
1 animated · complete, in your markup
import { motion } from "motion/react";

export function Alert() {
  return (
    <div className="flex h-[260px] w-full items-start justify-center rounded-lg bg-muted/40 p-6">
      <motion.div
        className="flex w-[320px] items-start gap-3 rounded-lg border border-destructive/40 bg-destructive/10 p-3.5"
        initial={{ opacity: 0, y: -12 }}
        animate={{ opacity: [0, 1], y: 0 }}
        transition={{
          opacity: { duration: 0.22, times: [0, 1], ease: "easeOut" },
          y: { type: "spring", stiffness: 260, damping: 24, mass: 1, restDelta: 0.01, restSpeed: 0.01 },
        }}
      >
        <span className="mt-1 size-2 shrink-0 rounded-full bg-destructive"></span>
        <span className="min-w-0">
          <span className="block text-[13px] font-medium text-foreground">Export failed</span>
          <span className="mt-0.5 block text-[12px] leading-relaxed text-muted-foreground">The spring is cut mid-overshoot, so Motion falls back to sampled keyframes.</span>
        </span>
      </motion.div>
    </div>
  );
}

Drops in from above and stops dead, no overshoot. A bouncy error message would feel like it's joking.

Open in editor