Back to Blog
CSSTransitionsAnimationUI

CSS Transitions Guide

Master CSS transitions for smooth state changes and interactive UI effects.

B
Bootspring Team
Engineering
October 3, 2018
6 min read

CSS transitions provide smooth animated changes between property values. Here's how to use them effectively.

Basic Transitions#

1/* Single property */ 2.button { 3 background-color: blue; 4 transition: background-color 0.3s; 5} 6 7.button:hover { 8 background-color: darkblue; 9} 10 11/* Multiple properties */ 12.card { 13 transform: scale(1); 14 box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); 15 transition: transform 0.3s, box-shadow 0.3s; 16} 17 18.card:hover { 19 transform: scale(1.05); 20 box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); 21} 22 23/* All properties (use sparingly) */ 24.element { 25 transition: all 0.3s; 26}

Transition Properties#

1.element { 2 /* Full syntax */ 3 transition-property: transform; 4 transition-duration: 0.3s; 5 transition-timing-function: ease-out; 6 transition-delay: 0.1s; 7 8 /* Shorthand */ 9 transition: transform 0.3s ease-out 0.1s; 10 11 /* Multiple transitions */ 12 transition: 13 transform 0.3s ease-out, 14 opacity 0.2s ease-in 0.1s, 15 background-color 0.3s; 16}

Timing Functions#

1.element { 2 /* Built-in timing functions */ 3 transition-timing-function: ease; /* Default, slow-fast-slow */ 4 transition-timing-function: ease-in; /* Slow start */ 5 transition-timing-function: ease-out; /* Slow end */ 6 transition-timing-function: ease-in-out; /* Slow start and end */ 7 transition-timing-function: linear; /* Constant speed */ 8 9 /* Step functions */ 10 transition-timing-function: steps(4); /* 4 discrete steps */ 11 transition-timing-function: steps(4, start); /* Jump at start */ 12 transition-timing-function: steps(4, end); /* Jump at end */ 13 transition-timing-function: step-start; /* Instant jump */ 14 transition-timing-function: step-end; /* Jump at end */ 15 16 /* Custom cubic-bezier */ 17 transition-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55); 18} 19 20/* Common custom curves */ 21.bounce { 22 transition-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55); 23} 24 25.smooth { 26 transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); 27} 28 29.snappy { 30 transition-timing-function: cubic-bezier(0.4, 0, 0, 1); 31}

Hover Effects#

1/* Fade */ 2.fade { 3 opacity: 1; 4 transition: opacity 0.3s; 5} 6 7.fade:hover { 8 opacity: 0.7; 9} 10 11/* Scale */ 12.scale { 13 transition: transform 0.3s; 14} 15 16.scale:hover { 17 transform: scale(1.1); 18} 19 20/* Lift */ 21.lift { 22 transition: transform 0.3s, box-shadow 0.3s; 23} 24 25.lift:hover { 26 transform: translateY(-5px); 27 box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2); 28} 29 30/* Color change */ 31.color { 32 color: #333; 33 transition: color 0.3s; 34} 35 36.color:hover { 37 color: #007bff; 38} 39 40/* Background with text */ 41.button { 42 background: #007bff; 43 color: white; 44 transition: background 0.3s, transform 0.1s; 45} 46 47.button:hover { 48 background: #0056b3; 49} 50 51.button:active { 52 transform: scale(0.98); 53}

Underline Effects#

1/* Expanding underline */ 2.link { 3 position: relative; 4 text-decoration: none; 5} 6 7.link::after { 8 content: ''; 9 position: absolute; 10 bottom: 0; 11 left: 50%; 12 width: 0; 13 height: 2px; 14 background: currentColor; 15 transition: width 0.3s, left 0.3s; 16} 17 18.link:hover::after { 19 width: 100%; 20 left: 0; 21} 22 23/* Slide from left */ 24.slide-link::after { 25 left: 0; 26 transform: scaleX(0); 27 transform-origin: left; 28 transition: transform 0.3s; 29} 30 31.slide-link:hover::after { 32 transform: scaleX(1); 33} 34 35/* Fill background */ 36.fill-link { 37 background: linear-gradient(to right, #007bff 50%, transparent 50%); 38 background-size: 200% 100%; 39 background-position: right; 40 transition: background-position 0.3s; 41} 42 43.fill-link:hover { 44 background-position: left; 45}

Card Transitions#

1/* Reveal overlay */ 2.card { 3 position: relative; 4 overflow: hidden; 5} 6 7.card-overlay { 8 position: absolute; 9 inset: 0; 10 background: rgba(0, 0, 0, 0.7); 11 opacity: 0; 12 transition: opacity 0.3s; 13} 14 15.card:hover .card-overlay { 16 opacity: 1; 17} 18 19/* Slide content */ 20.card-content { 21 transform: translateY(100%); 22 transition: transform 0.3s; 23} 24 25.card:hover .card-content { 26 transform: translateY(0); 27} 28 29/* Image zoom */ 30.card-image { 31 transition: transform 0.5s; 32} 33 34.card:hover .card-image { 35 transform: scale(1.1); 36}
1/* Dropdown */ 2.dropdown-menu { 3 opacity: 0; 4 visibility: hidden; 5 transform: translateY(-10px); 6 transition: opacity 0.3s, transform 0.3s, visibility 0.3s; 7} 8 9.dropdown:hover .dropdown-menu { 10 opacity: 1; 11 visibility: visible; 12 transform: translateY(0); 13} 14 15/* Mobile menu slide */ 16.mobile-menu { 17 transform: translateX(-100%); 18 transition: transform 0.3s ease-out; 19} 20 21.mobile-menu.open { 22 transform: translateX(0); 23} 24 25/* Menu item stagger (with JS) */ 26.menu-item { 27 opacity: 0; 28 transform: translateX(-20px); 29 transition: opacity 0.3s, transform 0.3s; 30} 31 32.menu-item.visible { 33 opacity: 1; 34 transform: translateX(0); 35} 36 37.menu-item:nth-child(1) { transition-delay: 0.1s; } 38.menu-item:nth-child(2) { transition-delay: 0.2s; } 39.menu-item:nth-child(3) { transition-delay: 0.3s; }

Form Transitions#

1/* Input focus */ 2.input { 3 border: 2px solid #ddd; 4 outline: none; 5 transition: border-color 0.3s, box-shadow 0.3s; 6} 7 8.input:focus { 9 border-color: #007bff; 10 box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25); 11} 12 13/* Floating label */ 14.form-group { 15 position: relative; 16} 17 18.floating-label { 19 position: absolute; 20 top: 50%; 21 left: 12px; 22 transform: translateY(-50%); 23 color: #999; 24 pointer-events: none; 25 transition: all 0.2s; 26} 27 28.input:focus + .floating-label, 29.input:not(:placeholder-shown) + .floating-label { 30 top: 0; 31 font-size: 0.75rem; 32 background: white; 33 padding: 0 4px; 34} 35 36/* Checkbox */ 37.checkbox-custom { 38 width: 20px; 39 height: 20px; 40 border: 2px solid #ddd; 41 border-radius: 4px; 42 transition: background 0.2s, border-color 0.2s; 43} 44 45.checkbox:checked + .checkbox-custom { 46 background: #007bff; 47 border-color: #007bff; 48}
1/* Fade in overlay */ 2.modal-overlay { 3 opacity: 0; 4 visibility: hidden; 5 transition: opacity 0.3s, visibility 0.3s; 6} 7 8.modal-overlay.open { 9 opacity: 1; 10 visibility: visible; 11} 12 13/* Scale and fade content */ 14.modal-content { 15 transform: scale(0.9); 16 opacity: 0; 17 transition: transform 0.3s, opacity 0.3s; 18} 19 20.modal-overlay.open .modal-content { 21 transform: scale(1); 22 opacity: 1; 23} 24 25/* Slide from bottom */ 26.modal-slide { 27 transform: translateY(100%); 28 transition: transform 0.3s ease-out; 29} 30 31.modal-slide.open { 32 transform: translateY(0); 33}

Performance#

1/* Use transform and opacity (GPU accelerated) */ 2.good { 3 transition: transform 0.3s, opacity 0.3s; 4} 5 6/* Avoid layout properties */ 7.bad { 8 transition: width 0.3s, height 0.3s, top 0.3s, left 0.3s; 9} 10 11/* will-change for complex transitions */ 12.complex { 13 will-change: transform; 14 transition: transform 0.3s; 15} 16 17/* Remove will-change after transition */ 18.element.transitioning { 19 will-change: transform; 20} 21 22/* Use translate instead of position */ 23.better { 24 transform: translateX(100px); 25 transition: transform 0.3s; 26} 27 28.worse { 29 left: 100px; 30 transition: left 0.3s; 31}

Accessibility#

1/* Respect reduced motion preference */ 2@media (prefers-reduced-motion: reduce) { 3 *, 4 *::before, 5 *::after { 6 transition-duration: 0.01ms !important; 7 } 8} 9 10/* Alternative: provide safe transitions */ 11.element { 12 transition: opacity 0.3s; /* Safe */ 13} 14 15@media (prefers-reduced-motion: no-preference) { 16 .element { 17 transition: opacity 0.3s, transform 0.3s; /* Full effect */ 18 } 19} 20 21/* Ensure focus states are visible */ 22.button:focus { 23 outline: 2px solid #007bff; 24 outline-offset: 2px; 25 /* Don't transition outline */ 26}

JavaScript Integration#

1// Trigger transitions with classes 2element.classList.add('active'); 3 4// Wait for transition to complete 5element.addEventListener('transitionend', (e) => { 6 if (e.propertyName === 'opacity') { 7 console.log('Opacity transition complete'); 8 } 9}); 10 11// Remove element after fade out 12function fadeOut(element) { 13 element.style.opacity = '0'; 14 element.addEventListener('transitionend', () => { 15 element.remove(); 16 }, { once: true }); 17} 18 19// Get transition duration from CSS 20const duration = parseFloat( 21 getComputedStyle(element).transitionDuration 22) * 1000;

Best Practices#

Properties: ✓ Use transform and opacity ✓ Specify properties explicitly ✓ Avoid "all" in production ✓ Use appropriate durations Timing: ✓ 200-300ms for UI feedback ✓ 300-500ms for emphasis ✓ ease-out for entering ✓ ease-in for exiting Accessibility: ✓ Respect prefers-reduced-motion ✓ Keep focus states visible ✓ Don't hide content permanently ✓ Test with keyboard Avoid: ✗ Transitioning layout properties ✗ Too long durations ✗ Transitions on page load ✗ Motion that causes discomfort

Conclusion#

CSS transitions provide smooth, performant animations for state changes. Focus on transform and opacity for best performance, use appropriate timing functions for natural movement, and respect user preferences for reduced motion. Keep durations short for UI responsiveness and use JavaScript to coordinate complex transition sequences.

Share this article

Help spread the word about Bootspring