How to Implement Asymmetric Enter/Exit Timing in UI Animations
Asymmetric enter/exit timing uses slower animations (200–400 ms) for deliberate user actions and faster animations (80–150 ms) for system responses to create natural, intuitive interactions.
This technique is a core standard in the emilkowalski/skills repository, which defines animation best practices for modern React applications. By distinguishing between the deliberate phase (when a user presses, holds, or confirms) and the system response (the immediate feedback that follows), you can craft interfaces that feel both responsive and polished.
Core Principles of Asymmetric Timing
The repository’s guidelines in skills/review-animations/STANDARDS.md establish that symmetric timing on press-and-release interactions feels mechanical. Instead, animations should follow the psychological weight of the action.
Deliberate Actions vs. System Responses
According to skills/emil-design-eng/SKILL.md, you must first identify the interaction type:
- Deliberate actions include opening a modal, expanding a menu, or confirming a destructive operation. These require slower, richer animations to acknowledge the user’s intent.
- System responses include toast dismissals, quick feedback states, or closing actions. These should snap quickly to maintain interface responsiveness.
Duration and Easing Guidelines
The skills/review-animations/SKILL.md file specifies concrete timing values:
- Enter (deliberate): Use durations between 200–400 ms with gentle easing such as
ease-outor a spring with low stiffness. - Exit (response): Use short durations between 80–150 ms with sharp easing like
ease-inor a high-stiffness spring.
As noted in skills/review-animations/STANDARDS.md, no hard-coded formula exists for every component. The exact values require iterative tweaking until the "deliberate-slow / snap-fast" rhythm feels natural.
Implementing Asymmetric Timing in Framer Motion
The skills/pick-ui-library/SKILL.md identifies Framer Motion (motion package) as the preferred library for UI-level animations. Define separate motion variants for enter and exit states:
import { motion } from "motion/react";
const variants = {
// Deliberate entry (slow, easing-out)
enter: {
opacity: 1,
scale: 1,
transition: {
duration: 0.35, // ≈ 350 ms
ease: "easeOut",
},
},
// System response exit (fast, easing-in)
exit: {
opacity: 0,
scale: 0.95,
transition: {
duration: 0.12, // ≈ 120 ms
ease: "easeIn",
},
},
};
export const Popover = ({ isOpen, onClose }) => (
<motion.div
className="popover"
initial={{ opacity: 0, scale: 0.9 }}
animate={isOpen ? "enter" : "exit"}
variants={variants}
style={{ transformOrigin: "var(--transform-origin)" }}
>
{/* content */}
<button onClick={onClose}>Close</button>
</motion.div>
);
The enter variant uses a longer duration with easeOut to create a sense of weight, while the exit variant snaps quickly with easeIn to clear the interface efficiently.
CSS-Only Implementation Strategy
If you cannot use Framer Motion, implement asymmetric timing using CSS classes and transitions. The repository standards support this fallback approach:
/* Deliberate entry - slow and gentle */
.popover.entering {
opacity: 1;
transform: scale(1);
transition: opacity 350ms ease-out, transform 350ms ease-out;
}
/* System response exit - fast and sharp */
.popover.exiting {
opacity: 0;
transform: scale(0.95);
transition: opacity 120ms ease-in, transform 120ms ease-in;
}
/* Anchor origin to the trigger element */
.popover {
transform-origin: var(--transform-origin);
}
Set the --transform-origin custom property via JavaScript based on the trigger element’s position to maintain spatial consistency.
Advanced Patterns: Staggered Lists with Asymmetric Timing
For list components where multiple items enter and exit, apply asymmetric timing at the individual item level while staggering the deliberate entrance:
const listVariants = {
hidden: {},
visible: {
transition: {
staggerChildren: 0.05,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 10 },
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.3,
ease: "easeOut",
},
},
exit: {
opacity: 0,
y: -10,
transition: {
duration: 0.12,
ease: "easeIn",
},
},
};
<motion.ul variants={listVariants} initial="hidden" animate="visible">
{items.map(item => (
<motion.li key={item.id} variants={itemVariants}>
{item.content}
</motion.li>
))}
</motion.ul>
Each list item inherits the asymmetric pattern: deliberate entry at 300 ms with easeOut and rapid exit at 120 ms with easeIn.
Spatial Consistency with Origin-Aware Animation
Asymmetric timing must pair with origin-aware animation to feel grounded. According to skills/animation-vocabulary/SKILL.md and skills/review-animations/SKILL.md, use a trigger-anchored transform-origin so animations emanate from the UI element that initiated the motion.
Set transform-origin: var(--transform-origin) in your styles and calculate the origin value based on the trigger element’s coordinates. This spatial consistency prevents the asymmetric timing from feeling disconnected or floating.
Summary
- Asymmetric enter/exit timing distinguishes deliberate user actions (slow, 200–400 ms) from system responses (fast, 80–150 ms).
- The
emilkowalski/skillsrepository defines these standards inskills/review-animations/STANDARDS.mdandskills/emil-design-eng/SKILL.md. - Use Framer Motion variants with separate
enterandexittransition configurations as the primary implementation method. - Maintain spatial consistency by anchoring
transform-originto the trigger element. - Iterate on timing values through trial-and-error rather than relying on rigid formulas.
Frequently Asked Questions
What is the recommended duration ratio between enter and exit animations?
The repository suggests roughly a 3:1 to 4:1 ratio, with enter animations typically lasting 200–400 ms and exit animations lasting 80–150 ms. However, skills/review-animations/STANDARDS.md emphasizes that these are starting points; the exact values require testing the component in isolation until the rhythm feels natural.
Why should deliberate actions animate slower than system responses?
Slower enter animations acknowledge the psychological weight of a deliberate choice, giving users time to perceive that their intent has been registered. Fast exit animations respect the user’s time by clearing the interface immediately after the system responds, preventing the interface from feeling sluggish or unresponsive.
Can I use CSS transitions instead of Framer Motion for asymmetric timing?
Yes. While skills/pick-ui-library/SKILL.md recommends Framer Motion for complex UI animations, the asymmetric timing principle works with any animation system. Use CSS classes (like .entering and .exiting) with different transition properties to achieve the same 200–400 ms deliberate entry and 80–150 ms system exit timing.
How do I determine the exact timing values for my specific component?
The repository guidelines explicitly state that no universal formula exists. You should run the component in isolation and adjust durations iteratively. Test with real users or observe the interaction yourself—the goal is a "deliberate-slow / snap-fast" rhythm that feels physically natural for your specific interaction pattern.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →