Micro-Interactions
When to Use
Every interactive element (buttons, cards, links, toggles) needs hover, active, and focus states. These are the difference between a flat prototype and a production interface.
Decision
| If you need... | Use... | Why |
|---|---|---|
| Card hover lift | translateY(-2px) + shadow increase |
Subtle lift implies interactivity |
| Button press feedback | scale(0.97) with instant duration |
Physical "push" feeling |
| Link hover indicator | Background-image underline reveal | Smoother than text-decoration transitions |
| Icon hover | filter: brightness(1.08) |
Avoids layout shift, works on any element |
| Toggle state | background-color + transform |
Color signals state, transform signals motion |
Professional vs Cheap Quality
| Quality | Translate | Duration | Easing | Effect |
|---|---|---|---|---|
| Professional | 2-4px | 150-200ms | ease-out / standard | Subtle, responsive, confident |
| Subtle/premium | 1-2px | 100-150ms | ease-out | Barely perceptible, high-end feel |
| Cheap/amateur | 10px+ | 800ms+ | linear or ease | Floaty, distracting, uncontrolled |
| Distracting | Any with bounce | 1000ms+ | ease-in-out with overshoot | Calls attention to the animation itself |
Pattern
Card hover:
.card {
transition:
transform var(--duration-normal) var(--ease-standard),
box-shadow var(--duration-normal) var(--ease-standard);
}
.card:hover {
transform: translateY(-2px);
box-shadow:
0 4px 6px hsl(var(--shadow-color) / 0.1),
0 8px 15px hsl(var(--shadow-color) / 0.1);
}
.card:active {
transform: translateY(0) scale(0.98);
transition-duration: var(--duration-quick);
}
Button press:
.btn {
transition:
background-color var(--duration-fast) var(--ease-standard),
transform var(--duration-fast) var(--ease-standard),
box-shadow var(--duration-fast) var(--ease-standard);
}
.btn:hover {
filter: brightness(1.08);
transform: translateY(-1px);
}
.btn:active {
transform: scale(0.97);
transition-duration: var(--duration-instant);
}
Link underline reveal:
.link {
text-decoration: none;
background-image: linear-gradient(currentColor, currentColor);
background-position: 0% 100%;
background-repeat: no-repeat;
background-size: 0% 1px;
transition: background-size var(--duration-normal) var(--ease-standard);
}
.link:hover {
background-size: 100% 1px;
}
Accessibility note: All hover interactions must have :focus-visible equivalents. Active press (scale(0.97)) should use --duration-instant so it never feels laggy. See Accessibility and Motion for prefers-reduced-motion handling.
Common Mistakes
- Animating only hover with no active state — the element feels unresponsive when clicked
- Using the same duration for hover-in and active — active must be faster (instant) for press feedback
translateY(-10px)on hover — too much movement; 2-4px is the professional range- Missing
transitiondeclaration on the base state — the hover snaps on and eases off (or vice versa) - Using
margin-toportopinstead oftransformfor hover lift — triggers layout, causes jank
See Also
- Motion Design Tokens — the easing and duration values used here
- Elevation and Shadows — shadow values for hover lift
- Animation Performance — why
transforminstead oftop - Accessibility and Motion — reduced-motion alternatives