Motion
Spring presets, easing and duration tokens, and reduced motion.
Principles
- Motion explains cause and effect: what opened, what changed, and where it came from.
- Animate
transformandopacity. Nothing resizes in a single frame; widths and heights follow on a spring. - Actions repeated many times a minute, like keyboard navigation, do not animate.
- Every animation keeps working when interrupted and has a reduced motion branch.
Springs
Springs live in lib/motion-tokens.ts and come with every Arc install.
PresetValuesUse it for
spring.snappyvisualDuration: 0.26, bounce: 0.12Presses, toggles, thumbs, and small indicators.spring.smoothvisualDuration: 0.4, bounce: 0Panels, height changes, and layout shifts. Never overshoots.spring.morphvisualDuration: 0.42, bounce: 0.16Shared highlights, shape changes, and widths that follow content.spring.responsivestiffness: 520, damping: 38Direct manipulation that must track the pointer closely.spring.gentlestiffness: 340, damping: 34Larger layout changes that should feel calm.Easing and duration
CSS transitions use the matching variables from the foundation.
TokenUse it for
--ease-standardDefault for UI transitions.--ease-enterElements arriving on screen.--ease-springA gentle settle, paired with --duration-spring.--duration-fast160ms. Hover, color, and small state changes.--duration-standard240ms. Panels and menus.--duration-considered480ms. Rare, introductory reveals.Using the tokens
toggle.tsx
import { motion, useReducedMotion } from "motion/react";import { motionTokens } from "@/lib/motion-tokens"; export function Thumb({ on }: { on: boolean }) { const reduce = useReducedMotion(); return ( <motion.span animate={{ x: on ? 20 : 0 }} transition={reduce ? { duration: 0 } : motionTokens.spring.snappy} /> );}card.module.css
.card { transition: background var(--duration-fast) var(--ease-standard);}Reduced motion
When someone asks for reduced motion, keep the end state, focus, and feedback, and remove travel, looping, and parallax. A short opacity change is fine. In CSS:
panel.module.css
@media (prefers-reduced-motion: reduce) { .panel { transition: opacity var(--duration-fast) var(--ease-standard); }}