Defining Animations with @keyframes
In Lesson 01 you wrote your first @keyframes rule — a simple fadeIn that moves opacity from 0 to 1. That covered the minimum. This lesson covers everything else: how to control what happens at intermediate points in an animation, how to animate more than one property at a time, and how to name your keyframe rules so they stay readable as your stylesheet grows.
The anatomy of a @keyframes rule
Section titled “The anatomy of a @keyframes rule”Every @keyframes rule has two parts: a name and a list of keyframe selectors.
@keyframes name { keyframe-selector { /* CSS declarations */ }}The name identifies the animation so you can apply it with the animation property later. It follows the same rules as CSS class names — no spaces, no reserved words, case-sensitive.
The keyframe selectors describe what the element’s styles should be at each point in the animation’s timeline. The timeline always runs from 0% (the start) to 100% (the end), regardless of how long the animation actually takes — duration is set separately on the element.
from and to
Section titled “from and to”from and to are shorthand for 0% and 100%. They are interchangeable with their percentage equivalents:
/* These two rules are identical */
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; }}
@keyframes fadeIn { 0% { opacity: 0; } 100% { opacity: 1; }}Use from/to when you only need two steps and the intent is obvious. Use percentages as soon as you add a third step — mixing from/to with percentages is valid but harder to scan.
A single property across multiple steps
Section titled “A single property across multiple steps”Adding a percentage step gives you a point on the timeline where you can specify an exact value. The browser interpolates between each adjacent pair of steps:
@keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.08); } 100% { transform: scale(1); }}The element starts at its natural size, grows to 108% at the halfway point, and returns to normal by the end. Because 0% and 100% match, this animation loops seamlessly — the end state is the same as the start state, so there is no jump between repetitions.
You are not limited to evenly spaced steps. Place them wherever the motion calls for it:
@keyframes bounceIn { 0% { transform: scale(0); opacity: 0; } 60% { transform: scale(1.1); opacity: 1; } 80% { transform: scale(0.95); } 100% { transform: scale(1); }}The element scales up quickly to 110%, overshoots slightly, settles back — a bounce. The 0%–60% span is long so the entry feels fast. The 60%–80% and 80%–100% spans are short so the overshoot corrects quickly.
Multiple properties in a single keyframe
Section titled “Multiple properties in a single keyframe”Each keyframe block is a regular CSS declaration block — you can set as many properties as you want in a single step:
@keyframes slideUpFade { 0% { opacity: 0; transform: translateY(24px); } 100% { opacity: 1; transform: translateY(0); }}Both opacity and transform start at their 0% values and arrive at their 100% values at the same moment. The browser interpolates each property independently along the same timeline.
Properties do not have to share the same keyframe stops. You can give opacity its own rhythm while transform follows a different one:
@keyframes revealUp { 0% { opacity: 0; transform: translateY(32px); } 40% { opacity: 1; } 100% { opacity: 1; transform: translateY(0); }}opacity reaches 1 at the 40% mark and stays there for the remainder of the animation. transform continues interpolating all the way to 100%. The element becomes fully visible early, then keeps sliding up into place — a subtle effect that makes the entrance feel less mechanical than both properties completing at the same instant.
Grouping selectors with the same declarations
Section titled “Grouping selectors with the same declarations”If two keyframe steps share the same declarations, you can group them with a comma — the same way you group regular CSS selectors:
@keyframes flash { 0%, 100% { opacity: 1; } 50% { opacity: 0; }}The element is fully visible at both 0% and 100%, and invisible at the midpoint — a clean one-flash blink.
Naming conventions
Section titled “Naming conventions”@keyframes names compete in a global namespace — every rule in every linked stylesheet shares the same pool of names. A collision silently overwrites the earlier rule. A few conventions help:
Use descriptive verbs that describe the motion, not the element. Name the movement, not where you plan to use it. fadeIn applies to any element that needs to fade in. heroFadeIn is awkwardly specific and harder to reuse.
/* Reusable — describes the motion */@keyframes fadeIn { }@keyframes slideUp { }@keyframes pulse { }
/* Avoid — too tied to a specific element */@keyframes heroFadeIn { }@keyframes cardHover { }Use camelCase. CSS is case-sensitive for @keyframes names. fadeIn and FadeIn are two different rules. Consistent camelCase prevents accidental mismatches when you reference the name in animation-name later.
Group all your @keyframes rules together. A dedicated /* === Animations === */ section at the bottom of the stylesheet — which you started in Lesson 01 — keeps them easy to find and update.
Exercise
Section titled “Exercise”In style.css, below the @keyframes fadeIn you wrote in Lesson 01, add two more keyframe rules:
1. A slide-up-fade combination:
@keyframes slideUpFade { 0% { opacity: 0; transform: translateY(24px); } 100% { opacity: 1; transform: translateY(0); }}2. A repeating pulse:
@keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.06); }}You now have three named animations in your stylesheet — fadeIn, slideUpFade, and pulse. None of them are applied to any element yet. In Lesson 03 you will learn the animation property and wire them up.
- A
@keyframesrule has a name and a list of keyframe selectors that describe styles at points along a 0%–100% timeline. fromequals0%andtoequals100%— use them for two-step animations; switch to percentages when you add a third step.- Keyframe steps can be placed at any percentage. The browser interpolates between each adjacent pair.
- Multiple properties can live in the same keyframe block and can have their own independent step patterns — a property does not need to appear at every keyframe stop.
- Group identical keyframe blocks with a comma:
0%, 100% { }. - Name animations after the motion they describe, not the element they are applied to. Use camelCase and group all
@keyframesrules in one section of the stylesheet.
Lesson 03 introduces the animation property — the shorthand that connects a @keyframes rule to an element and controls every aspect of how it plays: duration, timing, delay, iteration count, direction, fill mode, and play state.