← Back to all tutorials

Animation Fill Mode

Control what happens before and after an animation plays using animation-fill-mode — forwards, backwards, and both.

Animation Fill Mode

By default, when a keyframe animation ends, the element snaps back to its original state. The animation-fill-mode property controls what happens before the animation starts and after it ends.

The Problem

@keyframes fadeIn {
    from { opacity: 0; }
    to   { opacity: 1; }
}

.element {
    opacity: 0.5;  /* Original state */
    animation: fadeIn 1s ease;
}
/* After animation: element snaps back to opacity: 0.5 ❌ */

The element starts at opacity: 0.5, animates from 0 to 1, then jumps back to 0.5. Not what we want!

The Solution: animation-fill-mode

ValueBefore AnimationAfter Animation
none (default)Original stylesOriginal styles (snaps back)
forwardsOriginal stylesKeeps the final frame (to/100%)
backwardsApplies the first frame (from/0%)Original styles
bothApplies the first frameKeeps the final frame

forwards — Keep the End State

.element {
    opacity: 0;
    animation: fadeIn 1s ease forwards;
}
/* After animation: stays at opacity: 1 ✅ */

This is the most commonly used fill mode. The element stays in its animation end state.

backwards — Apply the Start State During Delay

@keyframes slideIn {
    from { transform: translateX(-100%); }
    to   { transform: translateX(0); }
}

.element {
    animation: slideIn 0.5s ease 1s backwards;
    /* 1s delay — during the delay, the element sits at translateX(-100%) */
}

Without backwards, the element would be visible in its original position during the delay, then suddenly jump to translateX(-100%) and animate. backwards fixes this by applying the first keyframe immediately.

both — Best of Both Worlds

.element {
    animation: slideIn 0.5s ease 1s both;
    /* During delay: applies from state (translateX(-100%))
       After animation: keeps to state (translateX(0)) */
}

both combines forwards and backwards. Use it when you have a delay and want to keep the final state.

Visual Timeline

DELAY ──────→ ANIMATION ──────→ AFTER
                                    
none:      [original]  [0%→100%]  [original]
forwards:  [original]  [0%→100%]  [100%]
backwards: [0%]        [0%→100%]  [original]
both:      [0%]        [0%→100%]  [100%]

Common Pattern: Staggered Entrance

@keyframes fadeSlideUp {
    from {
        opacity: 0;
        transform: translateY(20px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.card {
    opacity: 0; /* Initial hidden state */
    animation: fadeSlideUp 0.5s ease both;
}

.card:nth-child(1) { animation-delay: 0.1s; }
.card:nth-child(2) { animation-delay: 0.2s; }
.card:nth-child(3) { animation-delay: 0.3s; }
.card:nth-child(4) { animation-delay: 0.4s; }

Each card fades in one after another. both ensures they stay hidden during their delay and visible after.

Key Takeaways

  • Without fill-mode, elements snap back to their original state after animation
  • forwards — keep the final animation state (most common)
  • backwards — apply the first keyframe during the delay period
  • both — combines forwards and backwards (safest choice)
  • Use both with staggered delays for smooth entrance sequences