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-groupCSSTransition#
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}Modal Animation#
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.