Animations & Motion

Animations & Motion

Docs

A comprehensive guide to spring physics, layout transitions, and GPU-accelerated effects in ToolMate UI.

Overview

ToolMate UI provides two tiers of components:

  1. Zero-Dependency Functional Components: Pure Tailwind CSS and standard React hooks (useState, useRef). Zero extra npm packages required.
  2. 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:

PurposeTransition ConfigDescription
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 };