Skip to content
Laviyx Learn

Try

CSS course menu

CSS Keyframe Animations: @keyframes, animation Properties and Examples

Create CSS animations with @keyframes: animation-name, duration, timing function, delay, iteration count, direction, fill mode and play state, the animation shorthand, loading spinners and pulse effects, and accessible motion.

Intermediate7 min readUpdated
On this page

A transition animates between two states when something changes. A keyframe animation can run on its own, loop forever and pass through any number of steps.

@keyframes#

First, describe the animation with @keyframes and a name. Then attach it to an element with animation:

CSS
@keyframes slide-in {
  from { transform: translateX(-100%); opacity: 0; }
  to   { transform: translateX(0); opacity: 1; }
}

.toast {
  display: inline-block;
  padding: 10px 16px;
  background: #2f9e44;
  color: white;
  border-radius: 8px;
  animation: slide-in 0.6s ease-out;
}
HTML
<div class="toast">Saved successfully</div>
Result

The animation runs once, when the frame loads. Press Replay to run it again.

from and to are the same as 0% and 100%. You can add as many steps as you like:

CSS
@keyframes bounce {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-40px); }
}

.ball {
  width: 40px;
  height: 40px;
  margin-top: 50px;
  border-radius: 50%;
  background: #e8590c;
  animation: bounce 1s ease-in-out infinite;
}
HTML
<div class="ball"></div>
Result

The animation properties#

PropertyMeaningExample
animation-nameWhich @keyframes to usebounce
animation-durationLength of one cycle1s
animation-timing-functionSpeed curve (as for transitions)ease-in-out
animation-delayWait before starting (negative starts part-way in)0.5s
animation-iteration-countHow many times3, infinite
animation-directionnormal, reverse, alternate, alternate-reversealternate
animation-fill-modeKeep styles before/after the animationforwards, both
animation-play-statePause or runpaused

The shorthand: animation: name duration timing-function delay iteration-count direction fill-mode;. The first time value is the duration and the second the delay.

fill-mode#

By default, once an animation ends, the element snaps back to its normal styles. animation-fill-mode: forwards keeps the final keyframe's styles; backwards applies the first keyframe during the delay; both does both.

Practical examples#

A loading spinner#

CSS
@keyframes spin { to { transform: rotate(360deg); } }

.spinner {
  width: 36px;
  height: 36px;
  border: 4px solid #dbe4ff;
  border-top-color: #2f5fe0;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}
HTML
<div class="spinner" role="status" aria-label="Loading"></div>
Result

A spinner is only visual, so role="status" and a label tell screen reader users that something is loading.

A pulse that draws attention#

CSS
@keyframes pulse {
  0%   { box-shadow: 0 0 0 0 rgb(232 89 12 / 0.6); }
  100% { box-shadow: 0 0 0 16px rgb(232 89 12 / 0); }
}
.live {
  display: inline-block;
  margin: 16px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #e8590c;
  animation: pulse 1.5s ease-out infinite;
}
HTML
<span class="live"></span> Live now
Result

Staggered entrances#

A different animation-delay on each item makes them appear one after another:

CSS
@keyframes rise { from { opacity: 0; transform: translateY(12px); } }
li { animation: rise 0.5s ease-out both; }
li:nth-child(2) { animation-delay: 0.15s; }
li:nth-child(3) { animation-delay: 0.3s; }
HTML
<ul>
  <li>Learn HTML</li>
  <li>Learn CSS</li>
  <li>Learn JavaScript</li>
</ul>
Result

A keyframe with only from animates from those values to the element's normal styles.

Pausing on hover#

CSS
.marquee { animation: scroll 10s linear infinite; }
.marquee:hover { animation-play-state: paused; }

Accessibility#

  • Anything that moves, blinks or scrolls automatically for more than 5 seconds should have a way to pause or stop it (WCAG 2.2.2).
  • Never flash content more than three times per second; flashing can trigger seizures.
  • Turn off non-essential animation for users who ask for reduced motion:
CSS
@media (prefers-reduced-motion: reduce) {
  .ball, .live { animation: none; }
}

Practice#

Practice exercise

Create a "typing" indicator: three grey dots that each fade between 30% and 100% opacity in a 1.2s infinite loop, with the second and third dots delayed by 0.2s and 0.4s.

Show solution
CSS
@keyframes blink {
  0%, 100% { opacity: 0.3; }
  50% { opacity: 1; }
}
.typing { display: inline-flex; gap: 5px; padding: 10px 14px; background: #f1f3f5; border-radius: 16px; }
.typing span { width: 8px; height: 8px; border-radius: 50%; background: #666; animation: blink 1.2s infinite; }
.typing span:nth-child(2) { animation-delay: 0.2s; }
.typing span:nth-child(3) { animation-delay: 0.4s; }
HTML
<div class="typing" role="status" aria-label="Asha is typing"><span></span><span></span><span></span></div>
Result

Key takeaway#

Finished this lesson?