Spaceships do not suddenly appear on the radar screen - they fly in, pulse a signal, and smoothly move along their orbit. Animations in web interfaces work on the same principle: they add dynamics to the application and help the user understand what is happening. In this lesson, you will learn techniques for creating CSS animations in React applications - from simple transitions to complex keyframe sequences, and finally we will look at the React Transition Group library.
The
@keyframes rule allows you to define a sequence of animation steps. In styled-components, we use the keyframes helper, which generates a unique animation name and prevents conflicts:1import styled, { keyframes } from 'styled-components';
2
3// Animation definition - radar signal pulsing
4const radarPulse = keyframes`
5 0% {
6 transform: scale(1);
7 opacity: 1;
8 }
9 50% {
10 transform: scale(1.5);
11 opacity: 0.5;
12 }
13 100% {
14 transform: scale(2);
15 opacity: 0;
16 }
17`;
18
19const RadarDot = styled.div`
20 width: 20px;
21 height: 20px;
22 border-radius: 50%;
23 background: #00ff88;
24 animation: ${radarPulse} 2s ease-out infinite;
25`;Key elements:
keyframes is imported from styled-components (not from CSS)0%, 50%, 100%) or keywords (from, to)${radarPulse} in the animation propertyYou can define any number of keyframes and combine them in different components:
1const floatInSpace = keyframes`
2 0%, 100% { transform: translateY(0px); }
3 50% { transform: translateY(-15px); }
4`;
5
6const rotateOrbit = keyframes`
7 from { transform: rotate(0deg); }
8 to { transform: rotate(360deg); }
9`;
10
11const SpaceStation = styled.div`
12 animation: ${floatInSpace} 4s ease-in-out infinite;
13`;
14
15const OrbitRing = styled.div`
16 animation: ${rotateOrbit} 10s linear infinite;
17`;The
transition property in CSS is the simplest way to animate value changes. Instead of defining keyframes, you tell the browser: "when this property changes, do it smoothly." This is the ideal solution for hover effects, clicks, or component state changes.1const NavButton = styled.button`
2 background: #1a1a3e;
3 color: #00ff88;
4 padding: 12px 24px;
5 border: 2px solid #00ff88;
6 border-radius: 8px;
7 cursor: pointer;
8
9 /* Define what should be animated and how */
10 transition: all 0.3s ease;
11
12 &:hover {
13 background: #00ff88;
14 color: #1a1a3e;
15 transform: scale(1.05);
16 box-shadow: 0 0 20px rgba(0, 255, 136, 0.4);
17 }
18`;transition syntax:1transition: property duration timing-function delay;property - what we animate (all, background, transform, opacity...)duration - how long the transition lasts (0.3s, 200ms)timing-function - animation curve (ease, linear, ease-in-out, cubic-bezier())delay - start delay (optional)You can animate multiple properties with different parameters:
1const ShipPanel = styled.div`
2 opacity: 0.8;
3 transform: translateX(0);
4 background: #0a0a2e;
5
6 transition:
7 opacity 0.2s ease,
8 transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94),
9 background 0.3s ease;
10
11 &:hover {
12 opacity: 1;
13 transform: translateX(10px);
14 background: #1a1a4e;
15 }
16`;The
animation property is a more advanced version of transition. It allows for continuous, repeating animations, reverse playback, and control over every aspect of movement:1const warningBlink = keyframes`
2 0%, 100% { opacity: 1; }
3 50% { opacity: 0.3; }
4`;
5
6const WarningLight = styled.div`
7 width: 16px;
8 height: 16px;
9 border-radius: 50%;
10 background: #ff4444;
11
12 animation-name: ${warningBlink};
13 animation-duration: 1s;
14 animation-timing-function: ease-in-out;
15 animation-iteration-count: infinite;
16 animation-direction: alternate;
17`;Or in shorthand form:
1animation: name duration timing-function delay iteration-count direction fill-mode;Key parameters:
iteration-count - infinite (endless) or number of repetitions (3)direction - normal, reverse, alternate (back and forth)fill-mode - forwards (keep final state), backwards, bothplay-state - running or paused (playback control)In styled-components, you can create animations that respond to component props. This allows dynamically adjusting movement to the application state:
1const moveShip = keyframes`
2 from { transform: translateX(0); }
3 to { transform: translateX(100%); }
4`;
5
6const Spaceship = styled.div`
7 width: 60px;
8 height: 30px;
9 background: #4488ff;
10 clip-path: polygon(0% 50%, 30% 0%, 100% 50%, 30% 100%);
11
12 animation: ${moveShip} ${props => props.speed || 3}s linear infinite;
13 animation-play-state: ${props => props.isFlying ? 'running' : 'paused'};
14 opacity: ${props => props.isFlying ? 1 : 0.5};
15 transition: opacity 0.3s ease;
16`;
17
18function FlightControl() {
19 const [isFlying, setIsFlying] = useState(false);
20 const [speed, setSpeed] = useState(3);
21
22 return (
23 <div>
24 <Spaceship isFlying={isFlying} speed={speed} />
25
26 <button onClick={() => setIsFlying(!isFlying)}>
27 {isFlying ? 'Stop' : 'Fly'}
28 </button>
29
30 <input
31 type="range"
32 min="0.5"
33 max="5"
34 step="0.5"
35 value={speed}
36 onChange={(e) => setSpeed(e.target.value)}
37 />
38 <span>Speed: {speed}s per orbit</span>
39 </div>
40 );
41}You can also dynamically select different keyframes depending on state:
1const slideIn = keyframes`
2 from { transform: translateX(-100%); opacity: 0; }
3 to { transform: translateX(0); opacity: 1; }
4`;
5
6const slideOut = keyframes`
7 from { transform: translateX(0); opacity: 1; }
8 to { transform: translateX(100%); opacity: 0; }
9`;
10
11const Panel = styled.div`
12 animation: ${props => props.isVisible ? slideIn : slideOut} 0.4s ease forwards;
13`;This approach allows animating element entry and exit - the panel slides in from the left when appearing and slides out to the right when disappearing.
Native CSS cannot animate elements that are added to or removed from the DOM (e.g., through conditional rendering
{show && <Component />}). The React Transition Group library solves this problem by managing the enter/exit animation lifecycle.The most commonly used component is
CSSTransition:1import { CSSTransition } from 'react-transition-group';
2import { useState, useRef } from 'react';
3
4function NotificationPanel() {
5 const [showAlert, setShowAlert] = useState(false);
6 const nodeRef = useRef(null);
7
8 return (
9 <div>
10 <button onClick={() => setShowAlert(!showAlert)}>
11 {showAlert ? 'Hide alert' : 'Show alert'}
12 </button>
13
14 <CSSTransition
15 in={showAlert}
16 timeout={300}
17 classNames="alert"
18 unmountOnExit
19 nodeRef={nodeRef}
20 >
21 <div ref={nodeRef} className="alert-box">
22 Object detected on radar!
23 </div>
24 </CSSTransition>
25 </div>
26 );
27}CSSTransition automatically adds and removes CSS classes at the appropriate moments:1/* Entry - start */
2.alert-enter {
3 opacity: 0;
4 transform: translateY(-20px);
5}
6
7/* Entry - active phase */
8.alert-enter-active {
9 opacity: 1;
10 transform: translateY(0);
11 transition: opacity 300ms ease, transform 300ms ease;
12}
13
14/* Exit - start */
15.alert-exit {
16 opacity: 1;
17 transform: translateY(0);
18}
19
20/* Exit - active phase */
21.alert-exit-active {
22 opacity: 0;
23 transform: translateY(-20px);
24 transition: opacity 300ms ease, transform 300ms ease;
25}Key
CSSTransition props:in - boolean controlling whether the element is visibletimeout - animation duration (in ms), should match the CSS transitionclassNames - CSS class prefix (e.g., "alert" generates alert-enter, alert-enter-active, etc.)unmountOnExit - removes the element from DOM after exit animationnodeRef - ref to the animated element (recommended to avoid warnings)Let's summarize when to use each technique:
- for simple state changes:transition
+ @keyframes
- for complex and continuous animations:animation
React Transition Group - for component lifecycle animations:
Well-designed animations are like a ship's navigation system - they guide the user, provide feedback, and make the interface feel natural, like flying through the galaxy with a warp drive, not teleporting without warning.