# Keyframe Animations

A keyframe animation describes motion as a few key moments in a @keyframes rule and plays it with the animation property, so a spinner turns, a title slides in and a Start button pulses with no reader action needed.

Journeyman · CSS · Updated November 1, 2026

## After this lesson you can

- Write a @keyframes rule with from, to or percentages, and play it with the animation shorthand
- Set an animation's iteration count, direction and fill mode so it loops, goes back and forth or keeps its last frame
- Choose a transition or a keyframe animation for a job, and animate transform and opacity so the motion runs smoothly

## Key takeaways

- A @keyframes rule (read "at keyframes") names an animation and lists its key moments, from and to or percentages. The browser draws every frame in between.
- The animation property plays it: animation: pulse 1.6s ease-in-out infinite gives the name, the duration, the timing function and how many times.
- The iteration count says how many times it plays, the direction can make it go back and forth, and the fill mode keeps the first or last frame before or after it plays.
- A transition needs a change, such as hover, and goes between two looks. An animation plays by itself, can have many steps and can loop.
- Animate transform and opacity. The browser can change them without redoing the layout, so they stay smooth where width, top or margin can stutter.

A transition waits for something to change, like the mouse arriving on a button. But a lot of motion on a game screen happens on its own: the loading spinner going around while the level downloads, the title gliding in when the screen opens, the Start button gently pulsing to say "press me". That's a **keyframe animation**. You'll reach for one whenever something should move without the reader doing anything: a loading indicator, an attention-getting badge, a "new message" bounce or a gentle welcome.

## A flipbook: key moments and the frames in between

Draw a stick figure on the corner of every page of a notebook, each one a little different, then flip the pages fast: the figure moves. Animators work the same way. They draw the important poses, the **keyframes**, and the drawings in between get filled in.

In CSS, you write the keyframes and the browser draws every frame in between. A **`@keyframes`** rule (read "at keyframes") gives the animation a name and lists its key moments:

```css
@keyframes twinkle {
  from {
    opacity: 0.3;
  }
  to {
    opacity: 1;
  }
}
```

That reads "at keyframes twinkle: from opacity 0.3, to opacity 1". It describes a star going from dim to bright. On its own it does nothing yet: it's a flipbook nobody has flipped. `from` is the start and `to` is the end; you can also write them as `0%` and `100%`.

## Play it with the animation property

The **`animation`** property plays a `@keyframes` rule on an element. At the least, it needs the name and how long one play takes:

```css
.star {
  animation: twinkle 1.5s;
}
```

The name in `animation` must match the `@keyframes` name exactly, capital letters included, and this name is called the **animation name**. Leave out the duration and the animation lasts 0 seconds, so nothing seems to happen: the duration and the name are the two parts to check first when an animation doesn't play.

The `animation` property is a shorthand, like `transition`. Its parts can go in any order, but this order reads well: name, duration, timing function, delay, how many times, direction and fill mode.

```css
.title {
  animation: slide-in 600ms ease-out 300ms 1 normal both;
}
```

As with transitions, the first time is the duration and the second is the delay. Each part has its own property too: `animation-name`, `animation-duration`, `animation-timing-function`, `animation-delay`, `animation-iteration-count`, `animation-direction` and `animation-fill-mode`.

## More key moments: percentages

`from` and `to` give two moments. Percentages give as many as you need, each a point in the animation's time. Here's a Start button that swells and settles, again and again:

```css
.start-button {
  animation: pulse 1.6s ease-in-out infinite;
}

@keyframes pulse {
  50% {
    scale: 1.08;
    opacity: 0.85;
  }
}
```

Halfway through each 1.6 seconds, the button is 8% bigger and a little see-through. There's no `0%` or `100%` here: when a keyframe is missing, the browser uses the element's own style, so the pulse starts and ends at the button's normal size.

A loading spinner is a circle with one colored edge, turning around and around:

```css
.spinner {
  width: 3rem;
  height: 3rem;
  border: 0.4rem solid #2b2d5c;
  border-top-color: #ffd23f;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  from { rotate: 0deg; }
  to { rotate: 360deg; }
}
```

`linear` keeps the speed the same all the way round, so the spinner never seems to hesitate at the top.

## How many times, which way, and what stays

Three parts of the animation decide how it plays:

- **The iteration count** is how many times it plays: `1` (the default), `3`, or `infinite` for forever. The spinner and the pulse use `infinite`.
- **The direction** is which way each play goes: `normal` (from start to end), `reverse` (end to start), `alternate` (forward, then back, then forward again) and `alternate-reverse`. `twinkle` with `alternate` brightens, then dims, so the star never jumps from bright back to dim.
- **The fill mode** is what the element looks like when the animation isn't playing. With `none` (the default), it goes back to its own style when the animation ends. `forwards` keeps the last frame; `backwards` shows the first frame during the delay, before it starts; and `both` does both.

The fill mode matters for the title. It starts off the screen and slides in after a 300ms delay. Without `backwards` (or `both`), the title would sit in its normal place for those 300ms, then jump off the screen and slide in again:

```css
.title {
  animation: slide-in 600ms ease-out 300ms both;
}

@keyframes slide-in {
  from {
    translate: 0 -3rem;
    opacity: 0;
  }
}
```

**Try it.** Make a page with a `<div class="star">` that has a size and a background color, and give it `animation: twinkle 1.5s ease-in-out infinite alternate;` with the `twinkle` keyframes above. Then change `infinite` to `3`, and `alternate` to `normal`. Watch the difference: with `normal`, the star jumps back to dim at the start of each play.

## Transition or animation?

Both make things move, so how do you choose?

- **A transition** goes between two looks when something changes: the mouse arrives, the button gets focus, a class is added. It needs that change to start.
- **An animation** plays by itself, as soon as the element has it. It can have many key moments, loop forever and run backward.

A button that glows on hover is a transition. A spinner that turns while the game loads is an animation.

## Why transform and opacity are smooth

To show a page, the browser does three jobs. **Layout** works out where every box goes and how big it is. **Paint** colors in the pixels of each box. **Compositing** stacks the painted layers into the final picture, like layers in a photo app.

A smooth animation needs a new frame about 60 times a second, so the browser has about 16 milliseconds per frame. Animate `width`, `top` or `margin`, and every frame changes the layout, for this element and often for everything around it: the browser redoes all three jobs every frame, and on a slower phone it can't keep up, so the motion stutters. Animate `transform` (`translate`, `rotate`, `scale`) or `opacity`, and the layout doesn't change: the browser keeps the painted layer and moves it or fades it while compositing, often on the graphics chip.

So a loading bar that fills with `scale` instead of `width`, and a title that slides with `translate` instead of `top`, look the same and run far smoother.

A still picture can't show motion, so the examples here describe it. Paste them into a page and watch them play.

## Check yourself

2 questions to try on the lesson page.

## So what

With a few key moments and one `animation` line, you can make a screen feel alive: a spinner that says "wait, it's working", a title that welcomes you in, a button that invites a click. Keep the motion on `transform` and `opacity`, and it stays smooth on any device.

## Key terms

- [Keyframe animation](https://www.ask-codey.com/glossary/keyframe-animation)
- [Animation name](https://www.ask-codey.com/glossary/animation-name)
- [Animation iteration count](https://www.ask-codey.com/glossary/animation-iteration-count)
- [Animation fill mode](https://www.ask-codey.com/glossary/animation-fill-mode)
- [Compositing](https://www.ask-codey.com/glossary/compositing)

## Cheat sheets

- [Keyframe Animations](https://www.ask-codey.com/cheat-sheets/css-keyframe-animations-cheat-sheet)

## Related

- [Transitions: Smooth Changes](https://www.ask-codey.com/lessons/css-transitions)
- [Transforms: Move, Turn and Grow](https://www.ask-codey.com/lessons/css-transforms)
- [Motion That Respects the Reader: prefers-reduced-motion](https://www.ask-codey.com/lessons/css-reduced-motion)

Source: https://www.ask-codey.com/lessons/css-keyframe-animations
