Transition Patterns & Best Practices
Transitions are fundamental to modern interactive design. Effective patterns include hover feedback on buttons, smooth reveal/hide animations, staggered list animations, and coordinated multi-property transitions. This page covers proven patterns, accessibility considerations, and performance optimization for creating smooth, delightful user experiences.
Common transition patterns
CSS
/* 1. BUTTON HOVER FEEDBACK */
.button {
background: #3498db;
transform: scale(1);
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: background 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
cursor: pointer;
}
.button:hover {
background: #2980b9;
transform: scale(1.05);
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
.button:active {
transform: scale(0.98);
/* Pressed feeling */
}
/* 2. LINK UNDERLINE ANIMATION */
.link {
position: relative;
color: #333;
text-decoration: none;
transition: color 0.3s ease;
}
.link::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
width: 0;
height: 2px;
background: #3498db;
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.link:hover {
color: #3498db;
}
.link:hover::after {
width: 100%;
}
/* 3. SMOOTH REVEAL/HIDE */
.expandable {
max-height: 0;
overflow: hidden;
opacity: 0;
transition: max-height 0.3s ease, opacity 0.3s ease;
}
.expandable.open {
max-height: 500px;
opacity: 1;
}
/* 4. FADE IN/OUT */
.fade {
opacity: 0;
transition: opacity 0.4s ease;
}
.fade.visible {
opacity: 1;
}
/* 5. SLIDE IN/OUT */
.slide {
transform: translateX(-100%);
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.slide.active {
transform: translateX(0);
}Staggered list animations
CSS
/* Cascade effect: each item animates after the previous */
.list-item {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.5s ease, transform 0.5s ease;
}
/* Stagger timing with nth-child */
.list-item:nth-child(1) { transition-delay: 0s; }
.list-item:nth-child(2) { transition-delay: 0.1s; }
.list-item:nth-child(3) { transition-delay: 0.2s; }
.list-item:nth-child(4) { transition-delay: 0.3s; }
.list-item:nth-child(5) { transition-delay: 0.4s; }
.list-item.loaded {
opacity: 1;
transform: translateY(0);
}
/* Dynamic stagger with CSS custom properties */
.dynamic-list-item {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.5s ease, transform 0.5s ease;
transition-delay: calc(var(--index) * 0.1s);
}
.dynamic-list-item.loaded {
opacity: 1;
transform: translateY(0);
}
/* Grid stagger (diagonal/wave pattern) */
.grid-item {
opacity: 0;
transform: scale(0.9);
transition: opacity 0.4s ease, transform 0.4s ease;
/* JavaScript sets transition-delay based on row/column */
}
.grid-item.loaded {
opacity: 1;
transform: scale(1);
}
/* Alternate stagger (left-right wave) */
.item-left {
transform: translateX(-30px);
opacity: 0;
transition: transform 0.4s ease, opacity 0.4s ease;
}
.item-right {
transform: translateX(30px);
opacity: 0;
transition: transform 0.4s ease, opacity 0.4s ease;
}
.item-left.visible,
.item-right.visible {
transform: translateX(0);
opacity: 1;
}Multi-property coordination
CSS
/* Coordinating property timing for smooth effect */
/* Card entrance: fade and slide together */
.card {
opacity: 0;
transform: translateY(30px);
transition: opacity 0.5s ease, transform 0.5s ease;
}
.card.loaded {
opacity: 1;
transform: translateY(0);
}
/* Staggered reveal with different speeds */
.staggered {
opacity: 0;
transform: translate(20px, 20px);
transition:
opacity 0.3s ease 0.1s,
/* Opacity fades in after 0.1s delay, takes 0.3s */
transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
/* Transform starts immediately, takes 0.5s */
}
.staggered.visible {
opacity: 1;
transform: translate(0, 0);
}
/* Sequential property animation */
.sequential {
background: #f0f0f0;
border-color: #ddd;
color: #333;
transition:
background 0.2s ease,
border-color 0.3s ease 0.1s,
/* Border changes after background */
color 0.4s ease 0.2s;
/* Color changes last */
}
.sequential:hover {
background: #e8f4fd;
border-color: #3498db;
color: #3498db;
}
/* Circular reveal effect */
.circular-reveal {
clip-path: circle(0%);
opacity: 0;
transition: clip-path 0.6s ease, opacity 0.3s ease;
}
.circular-reveal.visible {
clip-path: circle(100%);
opacity: 1;
}Modal and dialog patterns
CSS
/* Smooth modal entrance */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0);
transition: background 0.3s ease;
}
.modal-overlay.open {
background: rgba(0, 0, 0, 0.5);
}
.modal-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0.9);
opacity: 0;
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease;
}
.modal-overlay.open .modal-content {
transform: translate(-50%, -50%) scale(1);
opacity: 1;
}
/* Slide-up modal */
.slide-up-modal {
position: fixed;
bottom: 0;
left: 0;
right: 0;
transform: translateY(100%);
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.slide-up-modal.open {
transform: translateY(0);
}
/* Drawer/sidebar slide-in */
.drawer {
position: fixed;
left: -100%;
top: 0;
width: 250px;
height: 100vh;
background: white;
transition: left 0.3s cubic-bezier(0.4, 0, 0.2, 1);
z-index: 100;
}
.drawer.open {
left: 0;
}
.drawer-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0);
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease;
z-index: 99;
}
.drawer.open ~ .drawer-overlay {
opacity: 1;
pointer-events: auto;
}Dropdown and menu patterns
CSS
/* Simple dropdown */
.dropdown-menu {
max-height: 0;
overflow: hidden;
opacity: 0;
transition: max-height 0.3s ease, opacity 0.2s ease;
}
.dropdown-trigger:hover ~ .dropdown-menu,
.dropdown-trigger:focus ~ .dropdown-menu,
.dropdown-menu:hover {
max-height: 300px;
opacity: 1;
}
/* Animated dropdown items */
.dropdown-item {
padding: 12px 16px;
transition: background 0.2s ease;
}
.dropdown-item:hover {
background: #f0f0f0;
}
/* Megamenu pattern */
.megamenu {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
max-height: 0;
overflow: hidden;
opacity: 0;
transition: max-height 0.4s ease, opacity 0.2s ease;
}
.megamenu-trigger:hover .megamenu,
.megamenu:hover {
max-height: 400px;
opacity: 1;
}
/* Breadcrumb navigation transitions */
.breadcrumb-item {
opacity: 0.6;
transition: opacity 0.2s ease;
}
.breadcrumb-item.active,
.breadcrumb-item:hover {
opacity: 1;
}Accessibility and reduced motion
CSS
/* Always respect prefers-reduced-motion */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* Alternative: reduce motion to minimal */
@media (prefers-reduced-motion: reduce) {
.element {
transition-duration: 0.1s;
/* Keep smooth, but much faster */
}
}
/* Focus states should be clear */
.button {
transition: background 0.3s ease, outline 0.2s ease;
}
.button:focus-visible {
outline: 3px solid #3498db;
outline-offset: 2px;
}
/* Ensure color contrast during transitions */
.interactive {
color: #333;
background: white;
transition: color 0.3s ease, background 0.3s ease;
}
.interactive:hover {
color: white;
background: #3498db;
/* Maintains sufficient contrast */
}
/* Avoid transitions that distract */
.too-distracting {
/* Avoid quick flashing or jarring changes */
transition: none;
}
.appropriate {
/* Smooth, purposeful transitions */
transition: background 0.3s ease;
}Performance best practices
CSS
/* 1. Prefer GPU-accelerated properties */
/* ✓ GOOD: GPU accelerated */
.good {
transition: transform 0.3s, opacity 0.3s;
}
/* ❌ AVOID: Causes reflows */
.avoid {
transition: width 0.3s, height 0.3s, padding 0.3s;
}
/* 2. Use will-change for complex animations */
.animated {
will-change: transform, opacity;
transition: transform 0.3s, opacity 0.3s;
}
/* 3. Group short transitions */
.combined {
transition: background 0.2s, color 0.2s, border-color 0.2s;
/* Same timing = combine into one rule */
}
/* 4. Keep durations reasonable */
.good-duration {
transition-duration: 0.3s;
/* 0.2s-0.4s is optimal for interactive feedback */
}
.too-long {
transition-duration: 3s;
/* Feels sluggish */
}
/* 5. Avoid transitioning pseudo-elements on hover */
.pseudo-anti-pattern {
/* This causes the browser to constantly check pseudo-element changes */
transition: all 0.3s;
}
.pseudo-better {
transition: background 0.3s, color 0.3s;
/* Specific properties only */
}
/* 6. Don't add transitions globally */
.bad-global {
/* Avoid: * { transition: all 0.3s; } */
/* Applies to every property change, wasteful */
}
.good-targeted {
transition: background 0.3s, transform 0.3s;
/* Only the properties you animate */
}Common anti-patterns to avoid
CSS
/* ❌ ANTI-PATTERN: transition: all */
.bad {
transition: all 0.3s;
/* Transitions every property, inefficient */
}
/* ✓ BETTER: Specific properties */
.good {
transition: background 0.3s, border 0.3s;
}
/* ❌ ANTI-PATTERN: Overly long durations */
.sluggish {
transition-duration: 2s;
/* Makes UI feel unresponsive */
}
/* ✓ BETTER: Standard duration */
.snappy {
transition-duration: 0.3s;
}
/* ❌ ANTI-PATTERN: No visual feedback */
.no-feedback {
/* Button with no transition or hover state */
background: #3498db;
}
/* ✓ BETTER: Clear feedback */
.clear-feedback {
background: #3498db;
transition: background 0.2s ease;
}
.clear-feedback:hover {
background: #2980b9;
}
/* ❌ ANTI-PATTERN: Conflicting transitions */
.conflicting {
transition: all 0.3s;
transition: background 0.5s; /* Overwrites previous */
}
/* ✓ BETTER: Consolidated */
.consistent {
transition: background 0.3s, color 0.3s, border 0.3s;
}
/* ❌ ANTI-PATTERN: Ignoring reduced motion */
@supports not (max-width: 0px) {
.ignoring {
transition: all 0.5s;
/* Doesn't check prefers-reduced-motion */
}
}
/* ✓ BETTER: Respects accessibility */
.accessible {
transition: all 0.3s;
}
@media (prefers-reduced-motion: reduce) {
.accessible {
transition: none;
}
}Note
Key transition patterns: button hover (0.2s), link underlines, smooth reveal/hide, fade in/out, cascading lists with staggered delays (0.1s increments). Always use GPU-accelerated properties (transform, opacity). Respect prefers-reduced-motion for accessibility. Keep durations 0.2-0.4s for feedback, 0.5-0.8s for reveals. Avoid transition: all—specify properties. Performance matters: fewer properties = smoother experience. Test on actual devices.
Next
CSS Animations overview: [CSS Animations Overview](/css/animations-intro).