Animations & Motion
DocsA comprehensive guide to spring physics, layout transitions, and GPU-accelerated effects in ToolMate UI.
Overview
ToolMate UI provides two tiers of components:
- Zero-Dependency Functional Components: Pure Tailwind CSS and standard React hooks (
useState,useRef). Zero extra npm packages required. - Animated Components (Motion ✨): Enriched with Framer Motion for spring physics, fluid layout morphing, exit transitions, and GPU-driven cursor effects.
1. Installing Animation Dependencies
To run the Animated (Framer Motion) component variants in your project, install framer-motion and lucide-react:
npm
npm install framer-motion lucide-react
pnpm
pnpm add framer-motion lucide-react
yarn
yarn add framer-motion lucide-react
bun
bun add framer-motion lucide-react
2. Spring Physics Conventions
ToolMate UI avoids stiff cubic-bezier easings in favor of natural spring physics that simulate real-world physical inertia:
| Purpose | Transition Config | Description |
|---|---|---|
| Micro-Interactions (Buttons, Toggles) | { type: "spring", stiffness: 600, damping: 30 } | Ultra-crisp, instantaneous tactical response |
| Overlays (Modals, Drawers) | { type: "spring", stiffness: 350, damping: 28 } | Balanced entrance with gentle deceleration |
| Hover Elevation (Cards, Badges) | { type: "spring", stiffness: 400, damping: 25 } | Smooth lift with subtle bounce |
Example
import { motion } from "framer-motion";
<motion.button
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
transition={{ type: "spring", stiffness: 500, damping: 25 }}
className="px-4 py-2 rounded-xl bg-primary text-primary-foreground font-semibold"
>
Interactive Button
</motion.button>
3. Layout Morphing with layoutId
For animated navigation pills, segmented tabs, and pagination indicators, ToolMate UI leverages Framer Motion’s shared layout engine (layoutId).
When the active item changes, Framer Motion automatically calculates the bounding rect delta and animates the background pill across elements without manual coordinate math:
{tabs.map((tab) => {
const isActive = activeTab === tab.id;
return (
<button key={tab.id} onClick={() => setActiveTab(tab.id)} className="relative px-3 py-1.5 text-xs font-semibold">
{isActive && (
<motion.div
layoutId="activePill"
transition={{ type: "spring", stiffness: 500, damping: 30 }}
className="absolute inset-0 bg-primary rounded-lg -z-10"
/>
)}
{tab.label}
</button>
);
})}
4. Exit Transitions with AnimatePresence
For Dialogs, Drawers, Toasts, and Tooltips, AnimatePresence allows components to smoothly animate out before being removed from the React virtual DOM:
import { motion, AnimatePresence } from "framer-motion";
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.15 }}
className="p-6 rounded-2xl border bg-card shadow-2xl"
>
Modal Content
</motion.div>
)}
</AnimatePresence>
5. GPU Acceleration & 60 FPS Performance
For intensive effects like the Spotlight Card cursor tracking, coordinate updates are streamed straight to CSS radial templates via useMotionValue and useMotionTemplate. This prevents React component re-renders on mousemove:
const mouseX = useMotionValue(0);
const mouseY = useMotionValue(0);
const background = useMotionTemplate`radial-gradient(400px circle at ${mouseX}px ${mouseY}px, rgba(99,102,241,0.2), transparent 80%)`;
6. Accessibility & Reduced Motion
Respecting user preferences is mandatory for WCAG 2.2 AA compliance. In environments where the user has enabled prefers-reduced-motion: reduce, animations should simplify into instantaneous state changes or subtle opacity fades.
You can wrap transitions with Framer Motion’s useReducedMotion() hook:
import { useReducedMotion } from "framer-motion";
const shouldReduceMotion = useReducedMotion();
const transition = shouldReduceMotion ? { duration: 0 } : { type: "spring", stiffness: 400, damping: 25 };