Transitions and animations: real, tasteful micro-interactions
A real product card that instantly snaps between states on hover feels noticeably less polished than one that transitions smoothly — but overdone motion is a genuine, real usability problem, not just an aesthetic one. This part covers both transition and @keyframes with that real balance in mind.
transition: real, smooth changes between states
.product-card {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transition: box-shadow 0.2s ease, transform 0.2s ease;
}
.product-card:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
transform: translateY(-2px);
}box-shadow, transform → which real properties to animate
0.2s → real duration
ease → real timing function — how the speed
changes over that duration, not linearWithout transition, the shadow and position change instantly the moment :hover applies — with it, the browser genuinely interpolates between the two real states smoothly over 0.2 seconds. This single declaration is what makes Bright Leaf Coffee's product cards feel deliberately responsive to interaction, rather than just visually different in two static states.
Why transform instead of animating top/left directly
/* less performant: animating position directly */
.product-card:hover {
margin-top: -2px;
}
/* genuinely more performant: transform */
.product-card:hover {
transform: translateY(-2px);
}Both produce a visually similar real lift effect, but they cost genuinely different amounts to render. Animating margin or top triggers real layout recalculation on every single frame of the transition — the browser has to re-measure and reposition potentially many elements. transform (and opacity) can typically be handled directly by the GPU without triggering layout at all, which is why they're the real, standard recommendation for anything animated — smoother in practice, and genuinely lighter on real device battery and performance.
@keyframes: real, multi-step animations
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.4; }
100% { opacity: 1; }
}
.loading-skeleton {
animation: pulse 1.5s ease-in-out infinite;
}@keyframes defines a real, named sequence of styles at specific points (here, 0%, 50%, and 100% through the animation), and animation applies it to an element with its own real duration, timing, and repeat behavior. This is exactly the real pattern behind a genuine loading skeleton — a placeholder shape that gently pulses while Bright Leaf Coffee's actual product data is still being fetched, giving a visitor real, honest feedback that something is happening rather than a static, ambiguous blank space.
Respecting prefers-reduced-motion: not optional
.product-card {
transition: box-shadow 0.2s ease, transform 0.2s ease;
}
@media (prefers-reduced-motion: reduce) {
.product-card {
transition: none;
}
.loading-skeleton {
animation: none;
}
}This directly continues part 14's introduction of this media feature — for a real, genuine subset of visitors, motion isn't a neutral aesthetic choice; it can cause real discomfort, disorientation, or in some documented cases trigger vestibular symptoms. Respecting prefers-reduced-motion isn't about removing all real interactivity feedback — the hover state above can still change color or border instantly, just without the animated transition — it's specifically about not forcing motion on a visitor who has explicitly, deliberately told their operating system they don't want it.
A real, blanket rule worth adopting: wrap every non-essential transition and animation declaration, or at minimum the site's own * selector, in a prefers-reduced-motion: reduce override, the same way this series' own accessibility fundamentals part treats other accessibility defaults as a genuine baseline, not an optional add-on for later.
A real, practical restraint principle
Worth animating: hover/focus feedback on interactive elements,
loading states, a real modal or menu opening
Worth reconsidering: animating on every single scroll, large or
frequent motion on elements not being directly interacted withThe genuine, practical line isn't "animation is good" or "animation is bad" — it's whether a specific real motion serves an actual, useful purpose (feedback, orientation, loading status) or exists purely for visual flourish. A site with restrained, purposeful motion reads as more polished than one with constant, ambient movement competing for attention — a real, easy trap once @keyframes starts feeling fun to reach for.
Next: pseudo-classes and pseudo-elements — :hover, :focus-visible, ::before, and ::after, the real mechanisms behind everything styled conditionally so far in this series.