CSS animation-direction Property

This property determines whether an animation plays forward, backward, or toggles back and forth between the two.

selector { animation-direction: normal | reverse | alternate | alternate-reverse; }
normal Plays the animation forward from 0% to 100% and resets to the start on every cycle.
reverse Plays the animation backward from 100% to 0% on every cycle.
alternate Plays the animation forward first, then plays it backward on even-numbered iterations.
alternate-reverse Plays the animation backward first, then plays it forward on even-numbered iterations.

Code Examples

A basic example showing a box that scales up and down smoothly using the alternate value to create a pulsing effect.

<style>
.pulse-box {
  width: 100px;
  height: 100px;
  background-color: #3498db;
  animation-name: pulse;
  animation-duration: 1s;
  animation-iteration-count: infinite;
  animation-direction: alternate;
}
@keyframes pulse {
  from { transform: scale(1); }
  to { transform: scale(1.2); }
}
</style>
<div class="pulse-box"></div>

An advanced example using JavaScript to toggle the animation-direction of a rotating element in real-time.

<style>
.gear {
  width: 100px;
  height: 100px;
  background-color: #e74c3c;
  animation: rotate 4s linear infinite;
}
@keyframes rotate {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}
</style>
<div id="myGear" class="gear"></div>
<button onclick="reverseGear()">Reverse Direction</button>
<script>
function reverseGear() {
  const gear = document.getElementById("myGear");
  const currentDir = window.getComputedStyle(gear).animationDirection;
  gear.style.animationDirection = currentDir === "normal" ? "reverse" : "normal";
}
</script>

Pro Tip

You can use this property to create sophisticated UI states without writing more keyframes. Instead of writing a separate closing animation, just reuse your opening animation and apply animation-direction: reverse via a class or JavaScript when the user closes the element.

Deep Dive

Think of this property like the playback head on a video player. By default, the head moves from the start of the timeline to the end, then jumps back to the start if it is set to loop. This jump can look like a visual glitch. By using alternate, you tell that playback head to move to the end, then move backward to the start, creating a seamless loop. It is important to realize that when playing in reverse, your timing functions like ease-in are also reversed. An ease-in becomes an ease-out when the direction is flipped because the browser is literally reading your keyframe timeline from right to left.

Best Practices

Use the alternate value when creating looping effects like a pulsing button or a floating icon to avoid the jarring snap back to the first frame. Always ensure your animation-iteration-count is set to infinite or a number greater than 1 when using alternate, otherwise you will never see the reverse phase of the animation.

Common Pitfalls

A common mistake is expecting alternate to work with an iteration count of 1. If the animation only runs once, there is no second iteration to play in reverse. Another thing to watch is that reverse starts the animation at the 100% keyframe state, which might cause a sudden jump in appearance the moment the page loads if your element's base CSS does not match the 100% keyframe.

Accessibility

Constant back-and-forth motion can be physically disorienting for users with vestibular disorders. Always wrap your complex animations in a media query that checks for prefers-reduced-motion to respect the user's system settings.

Dev Data Table: animation-direction property

default normal
animatable no
inherited no
experimental no
year_intro 2009
year_standard 2012
js_syntax_1 element.style.animationDirection = "alternate";
js_syntax_2 element.style.setProperty("animation-direction", "reverse");
js_note In JavaScript, ensure you use camelCase for the property name and always quote the string value.
browsers { "Chrome": 43, "Edge": 12, "Firefox": 16, "Safari": 9, "Opera": 30, "Chrome Android": 43, "Safari on iOS": 9, "Samsung Internet": "4.0", "Opera Mobile": 30 }
results render here...