CSS animations have evolved into a powerful tool for creating engaging user experiences on the modern web. Two properties that developers frequently overlook are animation-delay and animation-iteration-count. When combined strategically, these properties give you precise control over timing, allowing you to create animations that pause between iterations, stagger multi-element effects, and deliver polished interactions without relying on JavaScript.
This guide explores how to leverage these properties effectively in your Next.js projects and beyond.
Understanding the animation-delay Property
The animation-delay property specifies the amount of time to wait from applying the animation to an element before beginning to perform the animation. This property is fundamental for creating staggered animations, sequencing multiple effects, and improving perceived performance by delaying non-critical animations.
When building modern web interfaces, understanding how to control animation timing is essential for creating polished user experiences. Combined with other CSS techniques like CSS custom properties and CSS Grid layouts, these properties give you precise control over the timing and rhythm of your animations.
Key use cases:
- Delaying animation start for sequencing effects
- Creating staggered animations across multiple elements
- Improving page load perception by deferring non-critical animations
- Synchronizing multiple animations to start at different offsets
1/* Positive delay - wait before starting */2animation-delay: 2s;3animation-delay: 500ms;4 5/* Negative delay - start mid-animation */6animation-delay: -1s;7animation-delay: -500ms;8 9/* Multiple animations */10animation-delay: 0.5s, 1s, 1.5s;Negative Delay Values: Starting Mid-Animation
One of the most powerful features of animation-delay is support for negative values. When you specify a negative delay, the animation begins immediately but starts partway through its cycle. For example, animation-delay: -1s means the animation starts as if it has already been running for 1 second.
Practical applications:
- Synchronizing animations to a specific point in their cycle
- Creating "resume" effects where animations pick up where they left off
- Preloading animation states for JavaScript control
- Coordinating multiple elements to appear at the same animation state
Controlling Repetition with animation-iteration-count
The animation-iteration-count CSS property sets the number of times an animation sequence should be played before stopping. Combined with delay, this gives you complete control over animation cycles.
Core concepts:
- Default behavior: Animation plays once (count: 1)
- Finite repetition: Specify exact number of iterations
- Infinite looping: Use 'infinite' for continuous animations
- Partial iterations: Non-integer values play partial cycles
1/* Play once (default) */2animation-iteration-count: 1;3 4/* Play multiple times */5animation-iteration-count: 3;6animation-iteration-count: 10;7 8/* Play forever */9animation-iteration-count: infinite;10 11/* Partial iteration - plays half the cycle */12animation-iteration-count: 0.5;13 14/* Multiple animations */15animation-iteration-count: 2, infinite, 3;Creating Pauses Between Iterations
One of the most common requirements in animation design is creating a pause between iterations. CSS animations don't have a built-in "pause between iterations" property, but there are several effective techniques to achieve this effect.
Technique 1: Multiple Keyframes with Pause States
The cleanest approach is to incorporate pause states directly into your keyframe definition by holding a position for the desired duration:
@keyframes pulse-with-pause {
0%, 100% {
transform: scale(1);
opacity: 1;
}
25% {
transform: scale(1.1);
opacity: 0.8;
}
50% {
/* Pause state - hold the scaled position */
transform: scale(1.1);
opacity: 0.8;
}
75% {
transform: scale(1);
opacity: 1;
}
}
.element {
animation: pulse-with-pause 4s ease-in-out infinite;
}
In this example, the 25-50% range creates a visible pause between the scale up and scale down phases.
Technique 2: Sequential Animation Properties
For cleaner separation of animation and pause, use the animation's natural timing with adjusted keyframe percentages:
@keyframes breathe {
0%, 70%, 100% {
transform: scale(1);
}
35% {
transform: scale(1.1);
}
}
@keyframes idle {
0%, 35%, 70%, 100% {
opacity: 1;
}
50%, 60% {
opacity: 0.7;
}
}
.element {
animation: breathe 4s ease-in-out infinite;
}
This technique works well for "breathing" effects where the element expands, holds, contracts, and holds again.
Performance Considerations
CSS animations are generally GPU-accelerated and performant, but there are important considerations for optimal results. When building high-performance web applications, animation performance plays a crucial role in user experience and Core Web Vitals metrics.
Key Performance Guidelines
- Property selection: Animate only
transformandopacityfor best performance - will-change hint: Use sparingly to inform the browser about upcoming animations
- Animation duration impact: Longer animations use fewer resources per iteration
- Repetition overhead: Infinite animations have ongoing computational cost
Common Use Cases and Code Examples
Loading Indicators
@keyframes loading-dots {
0%, 20% { opacity: 0; transform: translateY(0); }
30%, 100% { opacity: 1; transform: translateY(-5px); }
}
.dot {
animation: loading-dots 1.5s ease-in-out infinite;
}
.dot:nth-child(1) { animation-delay: 0s; }
.dot:nth-child(2) { animation-delay: 0.2s; }
.dot:nth-child(3) { animation-delay: 0.4s; }
Card Hover with Repeated Pulse
@keyframes pulse {
0%, 100% {
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4);
}
50% {
box-shadow: 0 0 0 15px rgba(59, 130, 246, 0);
}
}
.card:hover {
animation: pulse 2s ease-out 3; /* Pulse 3 times on hover */
}
Staggered List Animation
@keyframes slide-up {
opacity: 0;
from {
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.list-item {
animation: slide-up 0.5s ease-out forwards;
opacity: 0;
}
.list-item:nth-child(1) { animation-delay: 0.1s; }
.list-item:nth-child(2) { animation-delay: 0.2s; }
.list-item:nth-child(3) { animation-delay: 0.3s; }
.list-item:nth-child(4) { animation-delay: 0.4s; }
.list-item:nth-child(5) { animation-delay: 0.5s; }
Accessibility Considerations
Not all users appreciate or can tolerate motion on web pages. Motion can cause discomfort or even seizures in some individuals with vestibular disorders. Following accessibility best practices ensures your animations enhance rather than hinder the user experience for all visitors.
Respecting User Preferences
/* Disable or reduce motion for users who prefer it */
@media (prefers-reduced-motion: reduce) {
.animated {
animation: none !important;
transition: none !important;
}
/* Or provide a subtle alternative */
.animated-fallback {
animation: subtle-pulse 2s ease-in-out infinite;
}
}
Guidelines for Animation Use
- Avoid auto-playing infinite animations near interactive elements
- Provide controls to pause or stop animations when possible
- Keep animations under 5 seconds unless they're subtle background effects
- Avoid large-scale transforms that can trigger motion sickness
Integration with Next.js
CSS animations integrate seamlessly with Next.js development. Here are some practical patterns:
Client-Side Animation Component
'use client';
export default function AnimatedList({ items }) {
return (
<div className="space-y-2">
{items.map((item, index) => (
<div
key={item.id}
className="animated-item"
style={{ animationDelay: `${index * 0.1}s` }}
>
{item.content}
</div>
))}
</div>
);
}
CSS Modules Pattern
import styles from './PulsingButton.module.css';
export default function PulsingButton({ children, onClick }) {
return (
<button className={styles.pulse} onClick={onClick}>
{children}
</button>
);
}
/* PulsingButton.module.css */
.pulse {
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
Advanced Techniques
Synchronizing Multiple Animations
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes scale-up { from { transform: scale(0.9); } to { transform: scale(1); } }
@keyframes slide-up { from { transform: translateY(10px); } to { transform: translateY(0); } }
.coordinated .element-1 { animation: fade-in 0.5s ease-out both; }
.coordinated .element-2 { animation: scale-up 0.5s ease-out 0.1s both; }
.coordinated .element-3 { animation: slide-up 0.5s ease-out 0.2s both; }
Dynamic Iteration Control with CSS Variables
:root {
--animation-iterations: 3;
--animation-delay: 0.5s;
}
.dynamic-animation {
animation: fade-in 1s ease-in-out var(--animation-iterations);
animation-delay: var(--animation-delay);
}
| Aspect | CSS Animations | JavaScript Animations |
|---|---|---|
| Performance | GPU-accelerated | Main thread |
| Control | Limited to CSS properties | Full programmatic control |
| Complexity | Simple for basic cases | More code but more flexibility |
| Debugging | Harder to inspect | Easier with dev tools |
| Best for | Looping, repeating effects | Complex sequencing, physics |
Summary and Best Practices
CSS keyframe animations with delay and iteration control offer powerful capabilities for creating polished user experiences:
- Start with clear timing goals: Define whether you need delays, pauses, or both
- Use negative delays for synchronization: They're more efficient than positive delays for staggered effects
- Incorporate pauses in keyframes: The cleanest approach for predictable timing
- Prioritize performance: Animate
transformandopacity, respectprefers-reduced-motion - Test across browsers: Both properties are well-supported but verify your target platforms
By mastering these properties, you can create sophisticated animations that enhance user engagement without sacrificing performance or accessibility.