Overview
Anicon uses a centralized animation configuration system to ensure consistent behavior across all icons. The configuration is defined in lib/animation-config.ts and provides presets for transitions, movements, rotations, scales, and sequences.
Animation Config Structure
The animation configuration exports several preset categories:
export const animationConfig = {
transitions: { /* Spring and tween presets */ },
distances: { /* Movement distances in pixels */ },
rotations: { /* Rotation amounts in degrees */ },
scales: { /* Scale factors */ },
sequences: { /* Shake/ring sequences */ },
durations: { /* Duration presets */ },
} as const ;
Transition Presets
Transitions define the timing and easing of animations:
Standard spring for most interactions {
type : "spring" ,
stiffness : 400 ,
damping : 17 ,
}
Bouncier spring for playful effects (hearts, stars) {
type : "spring" ,
stiffness : 300 ,
damping : 10 ,
}
Quick spring for snappy feedback {
type : "spring" ,
stiffness : 500 ,
damping : 20 ,
}
Tween for linear/predictable animations {
type : "tween" ,
duration : 0.3 ,
ease : "easeInOut" ,
}
Fast tween for tap responses {
type : "tween" ,
duration : 0.2 ,
ease : "easeOut" ,
}
Distance Presets
Movement distances in pixels for directional animations:
distances : {
small : 3 , // Subtle movement
medium : 5 , // Standard movement
large : 8 , // Prominent movement
}
Example Usage
import { animationConfig } from "@/lib/animation-config" ;
const arrowVariants = {
rest: { y: 0 },
hover: {
y: - animationConfig . distances . small ,
transition: animationConfig . transitions . spring
},
};
Rotation Presets
Rotation amounts in degrees:
rotations : {
small : 15 , // Subtle tilt
medium : 45 , // Quarter rotation
large : 90 , // Right angle
full : 360 , // Complete rotation
}
Example Usage
import { animationConfig } from "@/lib/animation-config" ;
const rotateVariants = {
rest: { rotate: 0 },
hover: {
rotate: animationConfig . rotations . small ,
transition: animationConfig . transitions . spring
},
};
Scale Presets
Scale factors for growing and shrinking:
scales : {
shrink : 0.9 , // Standard shrink
shrinkSmall : 0.95 , // Subtle shrink
grow : 1.1 , // Standard grow
growSmall : 1.05 , // Subtle grow
growLarge : 1.2 , // Prominent grow
}
Example Usage
import { animationConfig } from "@/lib/animation-config" ;
const scaleVariants = {
rest: { scale: 1 },
hover: {
scale: animationConfig . scales . grow ,
transition: animationConfig . transitions . springBouncy
},
tap: {
scale: animationConfig . scales . shrink ,
transition: animationConfig . transitions . tweenFast
},
};
Sequence Presets
Predefined animation sequences for shake, ring, and wiggle effects:
sequences : {
ring : [ 0 , - 20 , 20 , - 15 , 15 , - 10 , 10 , 0 ], // Bell ringing
shake : [ 0 , - 5 , 5 , - 5 , 5 , 0 ], // Horizontal shake
wiggle : [ 0 , - 3 , 3 , - 3 , 3 , 0 ], // Subtle wiggle
}
Example: Bell Icon
The bell icon uses the ring sequence for realistic ringing motion:
import { animationConfig } from "@/lib/animation-config" ;
const bellVariants = {
rest: { rotate: 0 },
hover: {
rotate: [ 0 , - 20 , 20 , - 15 , 15 , - 10 , 10 , - 5 , 5 , 0 ],
transition: {
duration: animationConfig . durations . ring ,
ease: "easeInOut"
},
},
};
Bell Icon
Arrow Icon
Loader Icon
"use client" ;
import { motion , useReducedMotion , type Variants } from "framer-motion" ;
export interface IconBellProps extends React . SVGProps < SVGSVGElement > {
size ?: number ;
strokeWidth ?: number ;
}
const bellVariants : Variants = {
rest: { rotate: 0 },
hover: {
rotate: [ 0 , - 20 , 20 , - 15 , 15 , - 10 , 10 , - 5 , 5 , 0 ],
transition: { duration: 0.6 , ease: "easeInOut" },
},
tap: {
rotate: [ 0 , - 25 , 25 , - 20 , 20 , - 15 , 15 , 0 ],
transition: { duration: 0.5 , ease: "easeInOut" },
},
};
export function IconBell ({
size = 24 ,
strokeWidth = 2 ,
className ,
... props
} : IconBellProps ) {
const prefersReducedMotion = useReducedMotion ();
return (
< motion.svg
xmlns = "http://www.w3.org/2000/svg"
width = { size }
height = { size }
viewBox = "0 0 24 24"
fill = "none"
stroke = "currentColor"
strokeWidth = { strokeWidth }
strokeLinecap = "round"
strokeLinejoin = "round"
variants = { bellVariants }
initial = { prefersReducedMotion ? false : "rest" }
whileHover = { prefersReducedMotion ? undefined : "hover" }
whileTap = { prefersReducedMotion ? undefined : "tap" }
style = { { originX: "50%" , originY: "0%" } }
className = { `outline-none select-none ${ className ?? "" } ` . trim () }
{ ... props }
>
< path d = "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" />
< path d = "M10.3 21a1.94 1.94 0 0 0 3.4 0" />
</ motion.svg >
);
}
"use client" ;
import { motion , useReducedMotion , type Variants } from "framer-motion" ;
export interface IconArrowUpProps extends React . SVGProps < SVGSVGElement > {
size ?: number ;
strokeWidth ?: number ;
}
const arrowVariants : Variants = {
rest: { y: 0 },
hover: {
y: [ 0 , - 2 , 0 ],
transition: {
duration: 1.5 ,
repeat: Infinity ,
ease: "easeInOut"
}
},
};
export function IconArrowUp ({
size = 24 ,
strokeWidth = 2 ,
className ,
... props
} : IconArrowUpProps ) {
const prefersReducedMotion = useReducedMotion ();
return (
< motion.svg
xmlns = "http://www.w3.org/2000/svg"
width = { size }
height = { size }
viewBox = "0 0 24 24"
fill = "none"
stroke = "currentColor"
strokeWidth = { strokeWidth }
strokeLinecap = "round"
strokeLinejoin = "round"
initial = "rest"
whileHover = "hover"
className = { `select-none ${ className ?? "" } ` . trim () }
{ ... props }
>
< motion.g variants = { prefersReducedMotion ? {} : arrowVariants } >
< path d = "m18 12-6-6-6 6" />
< path d = "M12 18V6" />
</ motion.g >
</ motion.svg >
);
}
"use client" ;
import { motion , useReducedMotion } from "framer-motion" ;
export interface IconLoaderProps extends React . SVGProps < SVGSVGElement > {
size ?: number ;
strokeWidth ?: number ;
}
export function IconLoader ({
size = 24 ,
strokeWidth = 2 ,
className ,
... props
} : IconLoaderProps ) {
const prefersReducedMotion = useReducedMotion ();
return (
< motion.svg
xmlns = "http://www.w3.org/2000/svg"
width = { size }
height = { size }
viewBox = "0 0 24 24"
fill = "none"
stroke = "currentColor"
strokeWidth = { strokeWidth }
strokeLinecap = "round"
strokeLinejoin = "round"
animate = { prefersReducedMotion ? {} : { rotate: 360 } }
transition = {
prefersReducedMotion
? {}
: {
repeat: Infinity ,
duration: 1 ,
ease: "linear" ,
}
}
className = { `select-none ${ className ?? "" } ` . trim () }
{ ... props }
>
< path d = "M12 2v4" />
< path d = "m16.2 7.8 2.9-2.9" />
< path d = "M18 12h4" />
< path d = "m16.2 16.2 2.9 2.9" />
< path d = "M12 18v4" />
< path d = "m4.9 19.1 2.9-2.9" />
< path d = "M2 12h4" />
< path d = "m4.9 4.9 2.9 2.9" />
</ motion.svg >
);
}
Duration Presets
Standardized durations in seconds:
durations : {
fast : 0.2 , // Quick transitions
normal : 0.3 , // Standard duration
slow : 0.5 , // Slower, deliberate
ring : 0.6 , // Bell ringing duration
}
Creating Custom Animations
You can import the animation config to create your own custom animations:
import { motion , useReducedMotion , type Variants } from "framer-motion" ;
import { animationConfig } from "@/lib/animation-config" ;
const customVariants : Variants = {
rest: {
scale: 1 ,
rotate: 0
},
hover: {
scale: animationConfig . scales . grow ,
rotate: animationConfig . rotations . small ,
transition: animationConfig . transitions . springBouncy ,
},
tap: {
scale: animationConfig . scales . shrink ,
transition: animationConfig . transitions . tweenFast ,
},
};
export function CustomIcon ({ size = 24 , strokeWidth = 2 , ... props }) {
const prefersReducedMotion = useReducedMotion ();
return (
< motion.svg
width = { size }
height = { size }
variants = { customVariants }
initial = { prefersReducedMotion ? false : "rest" }
whileHover = { prefersReducedMotion ? undefined : "hover" }
whileTap = { prefersReducedMotion ? undefined : "tap" }
{ ... props }
>
{ /* Your SVG paths */ }
</ motion.svg >
);
}
Animation Patterns
Beat/Pulse Animation
Used in the Heart icon for a beating effect:
const beatVariants = {
rest: { scale: 1 },
hover: {
scale: 1.1 ,
transition: {
duration: 0.5 ,
repeat: Infinity ,
repeatType: "reverse" ,
ease: "easeInOut"
}
}
};
Twinkle Animation
Used in the Star icon for a twinkling effect:
const twinkleVariants = {
rest: { opacity: 1 , scale: 1 },
hover: {
opacity: 0.7 ,
scale: 1.1 ,
transition: {
duration: 0.5 ,
repeat: Infinity ,
repeatType: "reverse" ,
ease: "easeInOut"
}
}
};
Continuous Rotation
Used in the Loader icon:
< motion.svg
animate = { { rotate: 360 } }
transition = { {
repeat: Infinity ,
duration: 1 ,
ease: "linear" ,
} }
>
{ /* Icon paths */ }
</ motion.svg >
Directional Movement
Used in arrow icons:
const arrowVariants = {
rest: { y: 0 },
hover: {
y: [ 0 , - 2 , 0 ],
transition: {
duration: 1.5 ,
repeat: Infinity ,
ease: "easeInOut"
}
},
};
TypeScript Types
The animation config exports TypeScript types for type-safe usage:
export type TransitionType = keyof typeof animationConfig . transitions ;
export type DistanceType = keyof typeof animationConfig . distances ;
export type RotationType = keyof typeof animationConfig . rotations ;
export type ScaleType = keyof typeof animationConfig . scales ;
// Usage
import type { TransitionType , ScaleType } from "@/lib/animation-config" ;
function useCustomAnimation ( transition : TransitionType , scale : ScaleType ) {
return {
scale: animationConfig . scales [ scale ],
transition: animationConfig . transitions [ transition ],
};
}
Best Practices
Use the presets - The animation config provides consistent, tested values that work well across different icons.
Consider performance - Avoid animating too many properties at once. Stick to transforms (scale, rotate, translate) and opacity for best performance.
Respect reduced motion - Always check useReducedMotion() and disable animations when users prefer reduced motion. See the Accessibility page for details.
Test on different devices - Animation feels different on mobile vs desktop. Test your animations on various devices.
Next Steps
Accessibility Learn how to make animations accessible
Basic Usage Back to basic usage guide