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}Navigation Transitions#
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}Modal Transitions#
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.