Motion
A small, opinionated motion system: two kinds of curves, five CSS utilities and four spring presets. That's all you need.
Principles
- Fast in, faster out. Entrances take 200–400ms, exits half that. Nobody waits for a menu to leave.
- Origin matters. Popovers grow from their trigger, sheets come from their edge, toasts from their corner.
- Springs for direct manipulation. Anything that follows a pointer or swaps position uses a spring so it can be interrupted mid-flight.
- CSS when possible. Radix exposes
data-state; keyframes do the rest with zero JavaScript.
Curves
Hit play. The difference between linear and expo is the difference between “works” and “feels good”.
linearRobotic. Avoid for UI.
ease-out-expoEnters fast, settles softly. Overlays, menus.
ease-springCSS linear() spring. Playful accents.
spring.snappyMotion spring. Indicators, toggles.
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
--ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);
--ease-in-quart: cubic-bezier(0.5, 0, 0.75, 0);
--ease-spring: linear(0, 0.009, 0.035 2.1%, 0.141 4.4%, …, 1);They're regular Tailwind utilities: ease-out-expo, ease-spring…
CSS utilities
Drop these on any Radix content element. They read data-state and data-side and handle enter and exit.
pl-popMenus, popovers, tooltips, hover cards. Scales from the trigger with a 4px nudge away from it.
pl-overlayFades backdrops in and out.
pl-dialogCentered modals: rises, unblurs and scales into place.
pl-slideSheets. Set data-side to top, right, bottom or left.
pl-collapseAccordion and collapsible content, animating to the measured height.
<PopoverPrimitive.Content
className="pl-pop origin-(--radix-popover-content-transform-origin) …"
/>Springs
Components that use Motion import shared presets from lib/motion.ts. Tune them once, and the whole library follows.
import { motion } from "motion/react"
import { spring } from "@/lib/motion"
<motion.span layoutId="indicator" transition={spring.snappy} />Reduced motion
The theme collapses every CSS animation and transition when the user asks for reduced motion. For Motion components, wrap your app in MotionConfig to do the same:
import { MotionConfig } from "motion/react"
<MotionConfig reducedMotion="user">{children}</MotionConfig>