Skip to content

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 motion

Import 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:

src/app/providers.tsx
<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

src/lib/motion.ts
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

ChangeUse
Hover, focus, colorCSS transition: duration-150 ease-standard
Entering and leavingmotion + AnimatePresence, transition.enter / transition.exit
Moving between placeslayout or layoutId, spring.snappy or spring.gentle
Drag and gesturesmotion's drag and whileTap
Simple loops (shimmer, marquee)CSS animations: animate-shimmer, animate-marquee
CSS for hover
// Hover and focus: a CSS transition with the same tokens<button className="transition-colors duration-150 ease-standard hover:bg-primary-hover" />

Example

notice.tsx
"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 transform and opacity; avoid animating layout properties like width by hand (use layout).
  • State each component's motion in its doc comment, as the library does.