How to Implement Asymmetric Enter/Exit Timing for Interactions in CSS
Asymmetric enter/exit timing creates intuitive micro-interactions by using slow, deliberate transitions for user-initiated states (enter/press) and fast, snappy transitions for system responses (exit/release).
The emilkowalski/skills repository establishes animation standards that prioritize asymmetric enter/exit timing to distinguish between deliberate user actions and immediate system feedback. This pattern, documented in skills/review-animations/SKILL.md and skills/emil-design-eng/SKILL.md, enables CSS-only implementations that feel responsive yet intentional without requiring JavaScript timers.
The Philosophy Behind Asymmetric Timing
Separating Intent from Feedback
The core architectural principle distinguishes between intent (the user’s deliberate gesture) and feedback (the UI’s acknowledgment). According to skills/review-animations/SKILL.md, deliberate actions should animate slower while system responses snap rapidly. This creates a psychological distinction between the user "deciding" (slow) and the system "reacting" (fast).
State-Driven Class Architecture
The implementation relies on modifier classes that apply only during specific interaction phases. In skills/emil-design-eng/SKILL.md, the pattern uses .active, .pressed, or .opening classes to trigger long-duration transitions, while the base element handles the rapid exit transition. This approach leverages the browser’s native transition engine rather than custom JavaScript animation loops.
Basic CSS Implementation
Button Press Pattern
The canonical example from skills/emil-design-eng/SKILL.md (lines 87-100) demonstrates a button that transitions slowly when pressed and snaps back instantly when released:
/* Base element – fast exit when releasing */
.button {
transition: transform 200ms ease-out, opacity 200ms ease-out;
}
/* Pressed state – slow, deliberate enter */
.button:active {
transition: transform 2s linear, opacity 2s linear;
}
Key implementation details:
- The base selector defines the exit animation (200ms with
ease-outfor immediacy) - The
:activepseudo-class defines the enter animation (2s withlinearto convey deliberation) - No JavaScript is required; the browser switches transitions automatically based on element state
Handling Long-Press Gestures
For interactions requiring programmatic state management, toggle modifier classes while keeping the CSS transition logic:
const btn = document.querySelector('.button');
let pressTimer;
btn.addEventListener('mousedown', () => {
btn.classList.add('pressing');
});
btn.addEventListener('mouseup', () => {
btn.classList.remove('pressing');
clearTimeout(pressTimer);
});
The CSS architecture remains identical to the :active example, substituting .button.pressing for :active to support custom gesture timing.
Advanced Component Patterns
Popover Asymmetric Transitions
Complex components like popovers require explicit state classes to manage enter/exit asymmetry. As documented in skills/review-animations/SKILL.md (lines 39-41), popovers should scale from their trigger point with timing that reflects user intent:
<div class="popover" id="myPopover">
<button class="trigger">Open</button>
<div class="content">Popover content</div>
</div>
.popover .content {
transform-origin: var(--transform-origin);
/* Fast exit (close) animation */
transition: transform 150ms ease-out, opacity 150ms ease-out;
}
/* Slow enter (open) animation */
.popover.opening .content {
transition-duration: 1.5s;
transition-timing-function: linear;
}
This pattern ensures the popover appears deliberately when the user initiates the action but disappears immediately when dismissed, matching the asymmetric enter/exit timing principle.
Accessibility and Performance Considerations
Respecting Reduced Motion Preferences
All asymmetric timing implementations must respect prefers-reduced-motion. As specified in skills/review-animations/SKILL.md (lines 37-38), disable transitions for users who require reduced motion:
@media (prefers-reduced-motion: reduce) {
.button,
.popover .content {
transition: none;
}
}
Input Device Targeting
Limit asymmetric animations to fine-pointer devices to prevent sluggish interactions on touch screens:
@media (hover: hover) and (pointer: fine) {
.button:active {
transition-duration: 2s;
}
}
Summary
- Asymmetric timing uses slow transitions (1-2s) for deliberate user actions and fast transitions (150-300ms) for system responses.
- CSS-only implementation relies on state-specific selectors (
:active,.opening) to switch transition properties without JavaScript timers. - Source files
skills/review-animations/SKILL.mdandskills/emil-design-eng/SKILL.mddefine the specific timing ratios and animation standards. - Accessibility requirements include
prefers-reduced-motionsupport and conditional application based on pointer precision.
Frequently Asked Questions
What is asymmetric enter/exit timing?
Asymmetric enter/exit timing is an animation pattern where the duration and easing of a state change differ depending on direction—typically slower when entering a state (user pressing) and faster when exiting (user releasing). This reflects the psychological reality that users spend more time deciding to act than waiting for the system to acknowledge the action has ended.
Why should enter animations be slower than exit animations?
Slower enter animations communicate deliberation and intent, giving users visual confirmation that the system recognizes their sustained input. Fast exit animations provide immediate feedback that the interaction has concluded, making the interface feel responsive. As noted in skills/review-animations/SKILL.md, this distinction helps users understand whether they are controlling the action or the system is responding to it.
Do I need JavaScript to implement asymmetric timing?
No. Pure CSS implementations using :active, :focus, or modifier classes handle the timing automatically. JavaScript is only necessary for complex gestures (like long-press detection) where you must manually toggle classes such as .pressing or .opening. The browser handles the transition interpolation natively based on which CSS transition definition applies to the current state.
How do I handle reduced motion preferences with asymmetric timing?
Wrap all transition definitions in a @media (prefers-reduced-motion: no-preference) query, or explicitly set transition: none within @media (prefers-reduced-motion: reduce). This ensures users with vestibular disorders or motion sensitivity receive an immediate state change without the slow deliberate animation that could cause discomfort.
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 →