Back to Blog
ReactAnimationTransitionsCSS

React Transition Group Guide

Master React Transition Group for smooth enter/exit animations in React components.

B
Bootspring Team
Engineering
February 27, 2020
6 min read

React Transition Group manages component enter/exit transitions. Here's how to use it.

Installation#

npm install react-transition-group npm install --save-dev @types/react-transition-group

CSSTransition#

1import { CSSTransition } from 'react-transition-group'; 2import { useRef, useState } from 'react'; 3 4function FadeExample() { 5 const [show, setShow] = useState(false); 6 const nodeRef = useRef(null); 7 8 return ( 9 <div> 10 <button onClick={() => setShow(!show)}>Toggle</button> 11 12 <CSSTransition 13 in={show} 14 timeout={300} 15 classNames="fade" 16 unmountOnExit 17 nodeRef={nodeRef} 18 > 19 <div ref={nodeRef} className="box"> 20 Fading content 21 </div> 22 </CSSTransition> 23 </div> 24 ); 25} 26 27// CSS 28/* 29.fade-enter { 30 opacity: 0; 31} 32 33.fade-enter-active { 34 opacity: 1; 35 transition: opacity 300ms ease-in; 36} 37 38.fade-exit { 39 opacity: 1; 40} 41 42.fade-exit-active { 43 opacity: 0; 44 transition: opacity 300ms ease-out; 45} 46*/

Transition States#

1import { CSSTransition } from 'react-transition-group'; 2 3// All transition states 4/* 5 classNames="my" 6 7 Entering: 8 - my-enter (initial enter state) 9 - my-enter-active (active enter state) 10 - my-enter-done (completed enter state) 11 12 Exiting: 13 - my-exit (initial exit state) 14 - my-exit-active (active exit state) 15 - my-exit-done (completed exit state) 16*/ 17 18function SlideExample() { 19 const [show, setShow] = useState(false); 20 const nodeRef = useRef(null); 21 22 return ( 23 <CSSTransition 24 in={show} 25 timeout={500} 26 classNames="slide" 27 nodeRef={nodeRef} 28 > 29 <div ref={nodeRef} className="panel"> 30 Content 31 </div> 32 </CSSTransition> 33 ); 34} 35 36// CSS 37/* 38.slide-enter { 39 transform: translateX(-100%); 40} 41 42.slide-enter-active { 43 transform: translateX(0); 44 transition: transform 500ms ease-out; 45} 46 47.slide-enter-done { 48 transform: translateX(0); 49} 50 51.slide-exit { 52 transform: translateX(0); 53} 54 55.slide-exit-active { 56 transform: translateX(-100%); 57 transition: transform 500ms ease-in; 58} 59*/

Custom Class Names#

1import { CSSTransition } from 'react-transition-group'; 2 3function CustomClassesExample() { 4 const [show, setShow] = useState(false); 5 const nodeRef = useRef(null); 6 7 return ( 8 <CSSTransition 9 in={show} 10 timeout={300} 11 classNames={{ 12 enter: 'animate-enter', 13 enterActive: 'animate-enter-active', 14 enterDone: 'animate-visible', 15 exit: 'animate-exit', 16 exitActive: 'animate-exit-active', 17 exitDone: 'animate-hidden', 18 }} 19 nodeRef={nodeRef} 20 > 21 <div ref={nodeRef}>Content</div> 22 </CSSTransition> 23 ); 24} 25 26// Timeout object for different durations 27function DifferentTimingsExample() { 28 const [show, setShow] = useState(false); 29 const nodeRef = useRef(null); 30 31 return ( 32 <CSSTransition 33 in={show} 34 timeout={{ 35 enter: 500, 36 exit: 300, 37 }} 38 classNames="slide" 39 nodeRef={nodeRef} 40 > 41 <div ref={nodeRef}>Content</div> 42 </CSSTransition> 43 ); 44}

TransitionGroup#

1import { TransitionGroup, CSSTransition } from 'react-transition-group'; 2import { useState, useRef } from 'react'; 3 4interface Item { 5 id: number; 6 text: string; 7} 8 9function ListExample() { 10 const [items, setItems] = useState<Item[]>([ 11 { id: 1, text: 'Item 1' }, 12 { id: 2, text: 'Item 2' }, 13 ]); 14 15 const addItem = () => { 16 const newItem = { 17 id: Date.now(), 18 text: `Item ${items.length + 1}`, 19 }; 20 setItems([...items, newItem]); 21 }; 22 23 const removeItem = (id: number) => { 24 setItems(items.filter(item => item.id !== id)); 25 }; 26 27 return ( 28 <div> 29 <button onClick={addItem}>Add Item</button> 30 31 <TransitionGroup component="ul" className="list"> 32 {items.map(item => ( 33 <CSSTransition 34 key={item.id} 35 timeout={300} 36 classNames="item" 37 > 38 <li> 39 {item.text} 40 <button onClick={() => removeItem(item.id)}>×</button> 41 </li> 42 </CSSTransition> 43 ))} 44 </TransitionGroup> 45 </div> 46 ); 47} 48 49// CSS 50/* 51.item-enter { 52 opacity: 0; 53 transform: translateY(-20px); 54} 55 56.item-enter-active { 57 opacity: 1; 58 transform: translateY(0); 59 transition: all 300ms ease-out; 60} 61 62.item-exit { 63 opacity: 1; 64} 65 66.item-exit-active { 67 opacity: 0; 68 transform: translateX(100%); 69 transition: all 300ms ease-in; 70} 71*/

SwitchTransition#

1import { SwitchTransition, CSSTransition } from 'react-transition-group'; 2import { useState, useRef } from 'react'; 3 4function TabSwitchExample() { 5 const [activeTab, setActiveTab] = useState('home'); 6 const nodeRef = useRef(null); 7 8 return ( 9 <div> 10 <nav> 11 <button onClick={() => setActiveTab('home')}>Home</button> 12 <button onClick={() => setActiveTab('about')}>About</button> 13 <button onClick={() => setActiveTab('contact')}>Contact</button> 14 </nav> 15 16 <SwitchTransition mode="out-in"> 17 <CSSTransition 18 key={activeTab} 19 timeout={200} 20 classNames="fade" 21 nodeRef={nodeRef} 22 > 23 <div ref={nodeRef} className="tab-content"> 24 {activeTab === 'home' && <HomeContent />} 25 {activeTab === 'about' && <AboutContent />} 26 {activeTab === 'contact' && <ContactContent />} 27 </div> 28 </CSSTransition> 29 </SwitchTransition> 30 </div> 31 ); 32} 33 34// Modes: 35// "out-in": current exits, then new enters (sequential) 36// "in-out": new enters, then current exits (overlapping) 37 38// CSS 39/* 40.fade-enter { 41 opacity: 0; 42} 43 44.fade-enter-active { 45 opacity: 1; 46 transition: opacity 200ms; 47} 48 49.fade-exit { 50 opacity: 1; 51} 52 53.fade-exit-active { 54 opacity: 0; 55 transition: opacity 200ms; 56} 57*/

Transition Component#

1import { Transition } from 'react-transition-group'; 2import { useRef } from 'react'; 3 4// For JavaScript-based animations 5function TransitionExample() { 6 const [show, setShow] = useState(false); 7 const nodeRef = useRef(null); 8 9 const duration = 300; 10 11 const defaultStyle = { 12 transition: `opacity ${duration}ms ease-in-out`, 13 opacity: 0, 14 }; 15 16 const transitionStyles = { 17 entering: { opacity: 1 }, 18 entered: { opacity: 1 }, 19 exiting: { opacity: 0 }, 20 exited: { opacity: 0 }, 21 }; 22 23 return ( 24 <div> 25 <button onClick={() => setShow(!show)}>Toggle</button> 26 27 <Transition 28 in={show} 29 timeout={duration} 30 nodeRef={nodeRef} 31 > 32 {state => ( 33 <div 34 ref={nodeRef} 35 style={{ 36 ...defaultStyle, 37 ...transitionStyles[state], 38 }} 39 > 40 Transitioning content 41 </div> 42 )} 43 </Transition> 44 </div> 45 ); 46} 47 48// With callbacks 49function CallbacksExample() { 50 const [show, setShow] = useState(false); 51 const nodeRef = useRef(null); 52 53 return ( 54 <Transition 55 in={show} 56 timeout={300} 57 nodeRef={nodeRef} 58 onEnter={() => console.log('Enter started')} 59 onEntering={() => console.log('Entering')} 60 onEntered={() => console.log('Entered')} 61 onExit={() => console.log('Exit started')} 62 onExiting={() => console.log('Exiting')} 63 onExited={() => console.log('Exited')} 64 > 65 {state => <div ref={nodeRef}>State: {state}</div>} 66 </Transition> 67 ); 68}
1import { CSSTransition } from 'react-transition-group'; 2import { useRef } from 'react'; 3 4function AnimatedModal({ isOpen, onClose, children }) { 5 const overlayRef = useRef(null); 6 const contentRef = useRef(null); 7 8 return ( 9 <CSSTransition 10 in={isOpen} 11 timeout={300} 12 classNames="modal-overlay" 13 unmountOnExit 14 nodeRef={overlayRef} 15 > 16 <div 17 ref={overlayRef} 18 className="modal-overlay" 19 onClick={onClose} 20 > 21 <CSSTransition 22 in={isOpen} 23 timeout={300} 24 classNames="modal-content" 25 appear 26 nodeRef={contentRef} 27 > 28 <div 29 ref={contentRef} 30 className="modal-content" 31 onClick={e => e.stopPropagation()} 32 > 33 {children} 34 </div> 35 </CSSTransition> 36 </div> 37 </CSSTransition> 38 ); 39} 40 41// CSS 42/* 43.modal-overlay { 44 position: fixed; 45 inset: 0; 46 background: rgba(0, 0, 0, 0.5); 47 display: flex; 48 align-items: center; 49 justify-content: center; 50} 51 52.modal-overlay-enter { 53 opacity: 0; 54} 55 56.modal-overlay-enter-active { 57 opacity: 1; 58 transition: opacity 300ms; 59} 60 61.modal-overlay-exit { 62 opacity: 1; 63} 64 65.modal-overlay-exit-active { 66 opacity: 0; 67 transition: opacity 300ms; 68} 69 70.modal-content-enter, 71.modal-content-appear { 72 opacity: 0; 73 transform: scale(0.9); 74} 75 76.modal-content-enter-active, 77.modal-content-appear-active { 78 opacity: 1; 79 transform: scale(1); 80 transition: all 300ms ease-out; 81} 82 83.modal-content-exit { 84 opacity: 1; 85 transform: scale(1); 86} 87 88.modal-content-exit-active { 89 opacity: 0; 90 transform: scale(0.9); 91 transition: all 300ms ease-in; 92} 93*/

Route Transitions#

1import { Routes, Route, useLocation } from 'react-router-dom'; 2import { TransitionGroup, CSSTransition } from 'react-transition-group'; 3 4function AnimatedRoutes() { 5 const location = useLocation(); 6 7 return ( 8 <TransitionGroup component={null}> 9 <CSSTransition 10 key={location.pathname} 11 timeout={300} 12 classNames="page" 13 > 14 <Routes location={location}> 15 <Route path="/" element={<Home />} /> 16 <Route path="/about" element={<About />} /> 17 <Route path="/contact" element={<Contact />} /> 18 </Routes> 19 </CSSTransition> 20 </TransitionGroup> 21 ); 22} 23 24// CSS 25/* 26.page-enter { 27 opacity: 0; 28 transform: translateX(100%); 29} 30 31.page-enter-active { 32 opacity: 1; 33 transform: translateX(0); 34 transition: all 300ms ease-out; 35} 36 37.page-exit { 38 opacity: 1; 39 transform: translateX(0); 40} 41 42.page-exit-active { 43 opacity: 0; 44 transform: translateX(-100%); 45 transition: all 300ms ease-in; 46} 47*/

Best Practices#

Usage: ✓ Use CSSTransition for CSS animations ✓ Use Transition for JS animations ✓ Use TransitionGroup for lists ✓ Use SwitchTransition for swapping Performance: ✓ Use transform and opacity ✓ Add will-change cautiously ✓ Use unmountOnExit to clean up ✓ Match CSS and timeout values Accessibility: ✓ Respect prefers-reduced-motion ✓ Keep animations brief ✓ Don't rely solely on animation ✓ Test with screen readers Avoid: ✗ Animating layout properties ✗ Very long animations ✗ Animations on page load ✗ Too many simultaneous animations

Conclusion#

React Transition Group provides low-level primitives for component transitions. Use CSSTransition for declarative CSS-based animations, Transition for JavaScript-controlled animations, and TransitionGroup for animating lists. Combine with SwitchTransition for smooth content switching.

Share this article

Help spread the word about Bootspring