CSS@keyframes

@keyframes rule

@keyframes defines the sequence of styles that change during a CSS animation. You specify keyframes as percentages (0%, 50%, 100%) or keywords (from, to), and the browser interpolates between them. Understanding keyframes is essential for creating smooth, complex animations.

Basic @keyframes syntax

CSS
<!-- @keyframes: define animation sequence -->
@keyframes fade {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}

.element {
  animation: fade 1s;
}

<!-- Element fades in over 1 second -->

/* Keyword syntax: from/to -->
@keyframes slide {
  from {
    transform: translateX(-100px);
  }
  to {
    transform: translateX(0);
  }
}

<!-- Element slides from left -->

/* Multiple keyframes: percentages -->
@keyframes bounce {
  0% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-50px);
  }
  100% {
    transform: translateY(0);
  }
}

<!-- Element bounces: up then down -->

/* Named animation: any name works -->
@keyframes my-custom-animation {
  /* ... */
}

@keyframes slideUp {
  /* ... */
}

<!-- Names can be dashed or camelCase -->
Keyframe percentages

Percentage

Meaning

Common Use

0%

Start state

Initial appearance

50%

Middle state

Intermediate effect

100%

End state

Final appearance

0%, 100%

Same state

Loop start/end match

CSS
<!-- Smooth color transition -->
@keyframes color-shift {
  0% {
    background: red;
  }
  50% {
    background: blue;
  }
  100% {
    background: green;
  }
}

<!-- Smoothly transitions red -> blue -> green -->

/* Multiple properties at keyframes -->
@keyframes complex {
  0% {
    opacity: 0;
    transform: translateY(20px);
  }
  50% {
    opacity: 0.5;
  }
  100% {
    opacity: 1;
    transform: translateY(0);
  }
}

<!-- Fade and move simultaneously -->

/* Different properties at different points -->
@keyframes staggered {
  0% {
    transform: translateX(0);
    opacity: 1;
  }
  50% {
    transform: translateX(100px);
    opacity: 1;
  }
  75% {
    transform: translateX(100px);
    opacity: 0;
  }
  100% {
    transform: translateX(0);
    opacity: 0;
  }
}

<!-- Move, pause, fade, return -->

/* Keyframes skip intermediate states -->
@keyframes quick-pulse {
  0%, 100% {
    opacity: 1;
  }
  50% {
    opacity: 0.3;
  }
}

<!-- Pulse: bright -> dim -> bright -->
Practical @keyframes animations

CSS
<!-- Fade in -->
@keyframes fadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

.element {
  animation: fadeIn 1s;
}

<!-- Fades in -->

/* Slide in from left -->
@keyframes slideInLeft {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(0);
  }
}

.panel {
  animation: slideInLeft 0.5s;
}

<!-- Slides in from left -->

/* Bounce effect -->
@keyframes bounce {
  0%, 100% { transform: translateY(0); }
  25% { transform: translateY(-20px); }
  50% { transform: translateY(0); }
  75% { transform: translateY(-10px); }
}

.ball {
  animation: bounce 1s infinite;
}

<!-- Ball bounces up and down -->

/* Loading pulse -->
@keyframes pulse {
  0%, 100% {
    opacity: 1;
  }
  50% {
    opacity: 0.5;
  }
}

.loader {
  animation: pulse 1s infinite;
}

<!-- Pulsing loader effect -->

/* Rotating spinner -->
@keyframes spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

.spinner {
  animation: spin 1s linear infinite;
}

<!-- Continuous rotation -->
Advanced keyframe techniques

CSS
<!-- Scale up entrance -->
@keyframes zoomIn {
  0% {
    opacity: 0;
    transform: scale(0.5);
  }
  100% {
    opacity: 1;
    transform: scale(1);
  }
}

.element {
  animation: zoomIn 0.5s ease-out;
}

<!-- Scales up while fading -->

/* Sequence animations: multiple at once -->
@keyframes enter {
  0% {
    opacity: 0;
    transform: translateY(20px);
  }
  100% {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes delayed {
  0% { opacity: 0; transform: translateX(-30px); }
  100% { opacity: 1; transform: translateX(0); }
}

.item:nth-child(1) { animation: enter 0.5s; }
.item:nth-child(2) { animation: enter 0.5s 0.1s backwards; }
.item:nth-child(3) { animation: enter 0.5s 0.2s backwards; }

<!-- Staggered entrance -->

/* Keyframes with easing applied per-keyframe -->
@keyframes custom-ease {
  0% {
    transform: translateX(0);
    animation-timing-function: ease-in;
  }
  50% {
    transform: translateX(100px);
    animation-timing-function: ease-out;
  }
  100% {
    transform: translateX(0);
  }
}

<!-- Different easing between keyframes -->

/* Complex chained animation -->
@keyframes slide-flip-fade {
  0% {
    transform: translateX(-100px);
    opacity: 0;
  }
  50% {
    transform: translateX(0) rotateY(0deg);
    opacity: 1;
  }
  75% {
    transform: translateX(50px) rotateY(180deg);
  }
  100% {
    transform: translateX(50px);
    opacity: 0;
  }
}

<!-- Combines translate, rotate, fade -->
Naming and organizing animations

CSS
<!-- Semantic animation names -->

@keyframes fadeIn { /* clear: fade in */ }
@keyframes slideUp { /* clear: slide up */ }
@keyframes spin { /* clear: rotating spin */ }
@keyframes bounce { /* clear: bouncy effect */ }

<!-- Descriptive names make intent obvious -->

/* Naming convention: verb + direction -->
@keyframes slideInLeft { /* slides in from left */ }
@keyframes slideOutRight { /* slides out to right */ }
@keyframes fadeInUp { /* fades while moving up */ }
@keyframes zoomInCenter { /* zooms from center */ }

<!-- Consistent, searchable naming -->

/* Group animations by category -->
/* Entrance animations */
@keyframes fadeIn { }
@keyframes slideInLeft { }
@keyframes zoomIn { }

/* Exit animations */
@keyframes fadeOut { }
@keyframes slideOutRight { }
@keyframes zoomOut { }

/* Continuous animations */
@keyframes spin { }
@keyframes pulse { }
@keyframes bounce { }

<!-- Organized by type -->

/* Reusable animation library -->
/* Could be in separate CSS file */
@keyframes fadeIn { /* ... */ }
@keyframes slideUp { /* ... */ }
@keyframes pulse { /* ... */ }

/* Use across project */
.modal { animation: fadeIn 0.3s; }
.alert { animation: slideUp 0.5s; }
.loader { animation: pulse 1s infinite; }

<!-- Consistent animations throughout -->
Note
`@keyframes` defines animation sequences using percentages (0%, 50%, 100%) or keywords (from, to). Properties transition smoothly between keyframe states. Use `animation-timing-function` to control easing. Named animations are reusable across elements. Keyframes with 0% and 100% states that match work well with `infinite` animations.
Next
Animation properties: [animation name & duration](/css/animation-name).