CSSTransition Patterns & Best Practices

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).