Keyframes allow you to define multiple intermediate steps in an animation sequence, giving you precise control over how properties change over time.
Understanding keyframes
A keyframe animation defines specific points along an animation timeline where property values are set. The browser automatically interpolates the values between keyframes.
CSS @keyframes syntax
Define an animation sequence using the @keyframes rule:
@keyframes fade-in {
0% { opacity: 0; }
100% { opacity: 1; }
}
.element {
animation: fade-in 1s ease;
}
Multi-step animations
Add intermediate keyframes for complex sequences:
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.2); }
100% { transform: scale(1); }
}
Using from and to
For simple two-state animations, you can use from and to:
@keyframes slide-in {
from { transform: translateX(-100%); }
to { transform: translateX(0); }
}
Real-world keyframe examples
Here are keyframe animations from the Animation Playground:
Fade in
Slide in
Rotate
Bounce
@keyframes fade-in {
0% { opacity: 0; }
100% { opacity: 1; }
}
Simple opacity transition from invisible to visible.@keyframes slide-in {
0% { transform: translateX(-100%); }
100% { transform: translateX(0); }
}
Element slides in from the left.@keyframes rotate {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
Continuous rotation animation.@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-30px); }
}
Bouncing motion with shared start and end states.
Animation properties
Control how keyframe animations play using animation properties:
.element {
animation-name: pulse;
animation-duration: 2s;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
animation-direction: alternate;
}
Or use the shorthand:
.element {
animation: pulse 2s ease-in-out infinite alternate;
}
Animation properties reference
- animation-name: The name of the @keyframes animation
- animation-duration: How long one cycle takes (e.g.,
1s, 500ms)
- animation-timing-function: Easing curve (see timing functions)
- animation-iteration-count: Number of repetitions (
1, 3, infinite)
- animation-direction: Playback direction (
normal, reverse, alternate, alternate-reverse)
- animation-delay: Wait before starting (e.g.,
0.5s)
- animation-fill-mode: Styles before/after animation (
none, forwards, backwards, both)
- animation-play-state: Control playback (
running, paused)
Timeline concepts
Think of keyframes as markers on a timeline. Each percentage represents a point in the animation’s duration.
Timeline visualization
0% 25% 50% 75% 100%
|-----------|-----------|-----------|-----------|
Start Keyframe Keyframe Keyframe End
With a 2-second animation:
- 0% = 0s
- 25% = 0.5s
- 50% = 1s
- 75% = 1.5s
- 100% = 2s
React implementation example
The Animation Playground provides an interactive timeline editor:
import { motion } from 'framer-motion'
import { useState } from 'react'
type KeyframeStep = {
id: string
time: number
properties: {
x: number
y: number
scale: number
rotate: number
opacity: number
}
}
export default function AnimationPlayground() {
const [keyframes, setKeyframes] = useState<KeyframeStep[]>([
{
id: '1',
time: 0,
properties: { x: 0, y: 0, scale: 1, rotate: 0, opacity: 1 }
}
])
const [isPlaying, setIsPlaying] = useState(false)
const getAnimationValues = (property: keyof KeyframeStep['properties']) => {
return keyframes.map(frame => frame.properties[property])
}
return (
<motion.div
animate={isPlaying ? {
x: getAnimationValues('x'),
y: getAnimationValues('y'),
scale: getAnimationValues('scale'),
rotate: getAnimationValues('rotate'),
opacity: getAnimationValues('opacity')
} : {}}
transition={{
duration: keyframes.length,
times: keyframes.map(k => k.time / keyframes.length),
repeat: Infinity
}}
className="w-20 h-20 bg-blue-500"
/>
)
}
Advanced techniques
Staggered keyframes
Different properties can peak at different times:
@keyframes complex-animation {
0% {
transform: scale(1) rotate(0deg);
opacity: 1;
}
25% {
transform: scale(1.2) rotate(0deg);
opacity: 1;
}
50% {
transform: scale(1.2) rotate(180deg);
opacity: 0.5;
}
75% {
transform: scale(1) rotate(180deg);
opacity: 0.5;
}
100% {
transform: scale(1) rotate(360deg);
opacity: 1;
}
}
Multiple animations
Combine several animations on one element:
.element {
animation:
fade-in 1s ease-out,
slide-in 1s ease-out,
rotate 2s linear infinite;
}
Animation fill modes
animation-fill-mode: forwards;
Retains the final keyframe styles after animation completes.animation-fill-mode: backwards;
Applies first keyframe styles during animation-delay.animation-fill-mode: both;
Combines forwards and backwards behavior.
Framer Motion keyframes
Framer Motion supports array-based keyframes:
<motion.div
animate={{
scale: [1, 1.5, 0.5, 1],
rotate: [0, 180, -180, 0],
opacity: [1, 0.2, 1]
}}
transition={{
duration: 2,
times: [0, 0.25, 0.75, 1],
repeat: Infinity,
repeatType: "loop"
}}
/>
The times array controls when each keyframe occurs (0 to 1).
Best practices
Avoid animating properties that trigger layout or paint. Stick to transform and opacity for best performance.
Keep keyframes simple
Fewer keyframes mean smoother interpolation:
Good:
@keyframes simple-bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-20px); }
}
Avoid:
@keyframes complex-bounce {
0% { transform: translateY(0); }
10% { transform: translateY(-2px); }
20% { transform: translateY(-5px); }
30% { transform: translateY(-9px); }
/* ...many more keyframes */
}
Use meaningful percentages
Align keyframes to natural breakpoints:
- 0%, 25%, 50%, 75%, 100% for quarters
- 0%, 33%, 66%, 100% for thirds
- 0%, 20%, 40%, 60%, 80%, 100% for fifths
Name animations clearly
/* Good */
@keyframes card-entrance { }
@keyframes button-press { }
@keyframes error-shake { }
/* Avoid */
@keyframes anim1 { }
@keyframes cool-thing { }
@keyframes my-animation { }
Keyframe animations can be very performant when done correctly:
- Animate only transform and opacity - These properties are GPU-accelerated
- Use will-change sparingly - Only when animating frequently
- Limit keyframe count - More keyframes = more calculations
- Avoid animating many elements - Use CSS instead of JavaScript when possible
CSS keyframe animations run on a separate thread from JavaScript, making them more performant than JS-based animations in many cases.