When building interactive UI components, apply these rules to ensure "feel," continuity, and responsiveness.
Core Philosophy
- Immediate: Response must happen in <100ms.
- Continuous: Elements should morph and travel, not teleport.
- Playful: Use physics (springs) over time-based curves for interactive elements.
- Subtle: Micro-interactions should whisper, not shout.
1. Interaction & Feedback
- MUST provide immediate visual feedback on
press(active state). - SHOULD use
scalefor press states.- Button/Card standard:
scale: 0.98or0.96. - Icon Button standard:
scale: 0.9.
- Button/Card standard:
- SHOULD use
whileTapin Motion oractive:scale-*in Tailwind. - NEVER block the UI thread during an interaction; trigger the animation and the action optimistically.
- MUST styling focus states for keyboard users (use
focus-visible).
2. Motion Physics (Framer Motion)
- SHOULD prefer
springovertweenfor physical interactions (drag, scale, layout). - Recommended Spring Configs:
- Snappy (Buttons/Toggles):
{ stiffness: 400, damping: 25 } - Fluid (Modals/Drawers):
{ stiffness: 300, damping: 30 } - Bouncy (Notifications):
{ type: "spring", stiffness: 500, damping: 30, mass: 1 }
- Snappy (Buttons/Toggles):
- NEVER use "lazy" springs (default stiffness < 100) for UI controls; it feels sluggish.
3. Spatial Continuity
- MUST use the
layoutprop inmotion/reactwhen an element changes position or size in the document flow. - MUST use
layoutIdwhen an element logically moves between two different sub-trees (e.g., a list item expanding into a detail view). - SHOULD use
layout="position"if only the position changes, to avoid distorted scale transforms on children. - MUST wrap removing items in
<AnimatePresence mode="popLayout">to prevent layout jumps.
4. Entrances & Exits
- MUST ensure exits are faster than entrances.
- Entrance:
duration: 0.3sto0.5s(allow eye to track). - Exit:
duration: 0.15sto0.2s(get out of the way).
- Entrance:
- SHOULD use
staggerChildrenfor lists (> 3 items).- Stagger delay:
0.05s-0.1s(keep it tight).
- Stagger delay:
- SHOULD combine
opacity+y(orscale) for standard entrances. Avoidxaxis unless lateral movement is implied.
5. Tailwind & CSS Transitions
- USE for simple state changes (colors, subtle shadows, opacity).
- Standard Transition:
transition-all duration-200 ease-out. - Hover:
hover:bg-opacity-80orhover:brightness-110> changing HSL values manually. - NEVER transition
heightorwidthwith CSS (causes reflow); use Framer MotionlayoutorResizeObservertechniques if needed.
6. Accessibility
- MUST respect
prefers-reduced-motion. - Implementation:
const shouldReduce = useReducedMotion(); const transition = shouldReduce ? { duration: 0 } : { type: "spring" };
Reference Patterns
The "Rubber Band" Button
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.95 }}
transition={{ type: "spring", stiffness: 400, damping: 17 }}
>
The "Shared Layout" Tab (The Magic Pill)
{isSelected && (
<motion.div
layoutId="active-pill"
className="absolute inset-0 bg-black rounded-full"
transition={{ type: "spring", bounce: 0.2, duration: 0.6 }}
/>
)}