Docs · Motion, icons and 3D
Motion
motion/react with shared duration, easing and spring tokens.
Animation uses motion 14 (the library formerly called Framer Motion), imported from motion/react. Durations, easings and springs come from shared tokens, so everything moves the same way.
Install
pnpm add motionImport from motion/react, not framer-motion. Files that use it need "use client".
Respect reduced motion
Wrap the app once. With reducedMotion="user", motion skips movement and scale for people who ask the OS for less motion, and keeps fades and color changes:
<MotionConfig reducedMotion="user" transition={transition.base}> {children}</MotionConfig>CSS animations are covered by a prefers-reduced-motion block in globals.css. Looping animations should also offer a still version; check useReducedMotion().
Motion tokens
import type { Transition, Variants } from "motion/react"; /** * Arise UI motion tokens. * * The same values exist as CSS variables in globals.css (--motion-*, --ease-*), * so CSS transitions and motion/react animations stay in sync. * * Rule of thumb: * - Hover, focus, color changes → CSS transitions with these tokens * - Enter/exit, layout, gestures, springs → motion/react with these tokens */ /** Durations in seconds (motion/react uses seconds). */export const duration = { /** 100ms — hover tints, list highlights */ instant: 0.1, /** 150ms — buttons, focus rings, small fades */ fast: 0.15, /** 200ms — menus, tooltips, toggles */ base: 0.2, /** 300ms — tabs, accordions, popovers */ moderate: 0.3, /** 450ms — drawers, carousels, sheets */ slow: 0.45, /** 700ms — charts, page-level entrances */ slower: 0.7,} as const; type Bezier = [number, number, number, number]; /** Cubic-bezier curves. */export const ease = { /** Most property changes */ standard: [0.2, 0, 0, 1] as Bezier, /** Things entering the screen */ out: [0.16, 1, 0.3, 1] as Bezier, /** Things leaving the screen */ in: [0.4, 0, 1, 1] as Bezier, /** Moving between two places */ inOut: [0.65, 0, 0.35, 1] as Bezier, /** Small overshoot for playful pops (CSS equivalent of a light spring) */ overshoot: [0.34, 1.56, 0.64, 1] as Bezier,}; /** Physics springs for interactive and layout motion. */export const spring = { /** Indicators, thumbs, segmented controls */ snappy: { type: "spring", stiffness: 520, damping: 38, mass: 0.8 }, /** Panels, cards, layout shifts */ gentle: { type: "spring", stiffness: 260, damping: 28 }, /** Likes, checkmarks, celebratory pops */ bouncy: { type: "spring", stiffness: 480, damping: 16 }, /** Draggable sheets */ sheet: { type: "spring", stiffness: 380, damping: 36 },} satisfies Record<string, Transition>; /** Ready-made transitions. */export const transition = { fast: { duration: duration.fast, ease: ease.standard }, base: { duration: duration.base, ease: ease.standard }, enter: { duration: duration.base, ease: ease.out }, exit: { duration: duration.fast, ease: ease.in }, moderate: { duration: duration.moderate, ease: ease.out }, slow: { duration: duration.slow, ease: ease.out }, move: { duration: duration.moderate, ease: ease.inOut },} satisfies Record<string, Transition>; /** Shared variants for common enter/exit patterns. */export const fadeUp: Variants = { hidden: { opacity: 0, y: 10 }, visible: { opacity: 1, y: 0, transition: transition.moderate }, exit: { opacity: 0, y: 6, transition: transition.exit },}; export const scaleIn: Variants = { hidden: { opacity: 0, scale: 0.96 }, visible: { opacity: 1, scale: 1, transition: transition.enter }, exit: { opacity: 0, scale: 0.96, transition: transition.exit },}; /** Parent variant that staggers its children. */export function staggerChildren(step = 0.05, delayChildren = 0): Variants { return { hidden: {}, visible: { transition: { staggerChildren: step, delayChildren } }, };}The same durations and curves exist as CSS variables (--motion-*, --ease-*) in globals.css. Change them together.
When to use what
| Change | Use |
|---|---|
| Hover, focus, color | CSS transition: duration-150 ease-standard |
| Entering and leaving | motion + AnimatePresence, transition.enter / transition.exit |
| Moving between places | layout or layoutId, spring.snappy or spring.gentle |
| Drag and gestures | motion's drag and whileTap |
| Simple loops (shimmer, marquee) | CSS animations: animate-shimmer, animate-marquee |
// Hover and focus: a CSS transition with the same tokens<button className="transition-colors duration-150 ease-standard hover:bg-primary-hover" />Example
"use client"; import { AnimatePresence, motion } from "motion/react";import { transition } from "@/lib/motion"; export function Notice({ open, children }: { open: boolean; children: React.ReactNode }) { return ( <AnimatePresence> {open && ( <motion.div initial={{ opacity: 0, y: 8 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: 4, transition: transition.exit }} // leave faster transition={transition.enter} // ease-out in > {children} </motion.div> )} </AnimatePresence> );}Rules
- Entrances ease out; exits ease in and run faster.
- Keep it short: 150–300ms for most UI, 450ms for drawers and sheets.
- Animate
transformandopacity; avoid animating layout properties likewidthby hand (uselayout). - State each component's motion in its doc comment, as the library does.