CSS Animations and Transitions — Complete Lesson

Welcome to CSS Animations & Transitions

Imagine you're watching a door open. It doesn't just teleport from closed to open — it moves smoothly. That smooth movement is essentially what CSS transitions and animations do to elements on a webpage.

Transitions are like a light switch with a dimmer — you flip it and it gradually brightens. Animations are like a movie scene — they play out a whole sequence you've scripted frame by frame.

What are CSS Transitions?

A transition moves an element from one style state to another — smoothly — when triggered by an event (like hovering). You define the start state and end state; CSS fills in the in-between.

What are CSS Animations?

An animation lets you define a full sequence of style changes that play automatically, loop, reverse, and more. You script every step using @keyframes.

Why do they matter?

Real-world uses

Where you see itWhat's happening
Hover over a linkTransition: color or underline changes smoothly
Loading spinnerAnimation: element rotates infinitely
Dropdown menu opensTransition: height or opacity fades in
Progress bar fillsTransition: width grows over time
Notification badgeAnimation: scales and pulses on loop
Page hero imageAnimation: fades in or slides up on load
Before we continue — can you think of an animation you've seen on a website today? What was it doing?
Throughout this lesson, each section builds on the last. We start with transitions (simpler, event-triggered), then move to animations (more powerful, self-running). Both use the same underlying idea: changing CSS properties over time.

CSS Transitions

A transition watches a CSS property and, when that property changes, it smoothly animates it from the old value to the new value over a specified duration.

Think of it like a thermostat. When you change the target temperature, the room doesn't instantly jump to 22°C — it gradually warms up. The transition is that gradual warmup.

How it works

  1. An element has an initial style
  2. Something triggers a change (usually :hover, :focus, or JS toggling a class)
  3. CSS smoothly interpolates between the two states

All transition properties

PropertyWhat it controlsExample value
transition-propertyWhich CSS property to animatebackground-color
transition-durationHow long the transition takes0.3s
transition-timing-functionSpeed curve (ease in, ease out, etc.)ease-in-out
transition-delayWait before starting0.1s
transitionShorthand for all fourall 0.3s ease 0.1s

The shorthand syntax

/* property  duration  timing-function  delay */
transition: background-color 0.3s ease-in-out 0s;

/* multiple transitions */
transition: background-color 0.3s ease, transform 0.2s ease-out;

Live demos — hover each element

Button hover
Hover me
Card lift
🖼
Image scale

Example 1: Button with background and transform

/* CSS */
.button {
  background: #7F77DD;
  color: white;
  padding: 10px 24px;
  border-radius: 8px;
  transition: background 0.3s ease, transform 0.2s ease;
}

.button:hover {
  background: #534AB7;
  transform: scale(1.05);
}
Output: When you hover, the button darkens AND slightly grows. Both changes happen at the same time but with their own durations.

Example 2: Card lift effect

/* CSS */
.card {
  box-shadow: 0 2px 8px rgba(0,0,0,0.08);
  transition: transform 0.4s ease, box-shadow 0.4s ease;
}

.card:hover {
  transform: translateY(-8px);
  box-shadow: 0 12px 24px rgba(0,0,0,0.15);
}

Example 3: Transition all properties

/* Quick way to animate everything */
.element {
  transition: all 0.3s ease-in-out;
}

/* WARNING: 'all' can be expensive for performance.
   Prefer naming specific properties. */
Using transition: all will animate every changing property. This can cause unexpected animations on things like height or display. Be specific when you can.

Example 4: Using transition-delay for staggered effects

.nav-item:nth-child(1) { transition: opacity 0.3s ease 0s; }
.nav-item:nth-child(2) { transition: opacity 0.3s ease 0.1s; }
.nav-item:nth-child(3) { transition: opacity 0.3s ease 0.2s; }
Output: Each nav item fades in with a slight 0.1s stagger — giving a cascading reveal effect.
Can you explain in your own words the difference between transition-duration and transition-delay?

What CAN be transitioned?

Any property with a numeric or color mid-point — things like:

You CANNOT transition: display (block↔none), font-family, or background-image. For display toggling, use opacity + visibility instead.

CSS Animations

While transitions react to an event, animations run on their own. You define a full timeline of states using @keyframes, and the browser plays it out — automatically, on loop, in reverse, delayed, or paused.

A transition is like a light switch with a dimmer. An animation is like a stage director's cue card — "at 0% of the scene, the actor stands here; at 50%, they walk left; at 100%, they bow." The director controls the entire performance.

Transitions vs. Animations at a glance

FeatureTransitionsAnimations
TriggerNeeds an event (hover, click, class)Runs automatically
KeyframesOnly start and endUnlimited keyframes
LoopingNoYes, controllable
ComplexitySimple (A → B)Complex (A → B → C → D)
Use forHover/focus statesLoaders, storytelling, onload

Understanding @keyframes

A keyframe rule defines what the element looks like at each stage of the animation. You name it, then reference that name.

@keyframes myAnimation {
  0%   { opacity: 0; transform: translateY(20px); }
  50%  { opacity: 0.5; }
  100% { opacity: 1; transform: translateY(0); }
}

/* You can also use 'from' and 'to' for simple cases */
@keyframes fadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

All animation properties

PropertyWhat it controlsExample
animation-nameWhich @keyframes to usespin
animation-durationLength of one cycle1s
animation-timing-functionSpeed curveease-in-out
animation-delayWait before starting0.5s
animation-iteration-countHow many times to runinfinite or 3
animation-directionNormal, reverse, alternatealternate
animation-fill-modeState before/after playingboth
animation-play-stateRunning or pausedpaused
animationShorthand for allspin 1s linear infinite

The shorthand

/* name  duration  timing  delay  iterations  direction  fill-mode */
animation: fadeIn  0.5s    ease    0.2s   1           normal     forwards;

animation-fill-mode explained

ValueBehaviour
noneSnaps back to original style after animation ends
forwardsStays at the last keyframe state
backwardsApplies first keyframe style during delay
bothCombines forwards and backwards behaviour

Live animation examples

Loading spinner
Bounce
Fade
Fade pulse
Slide
Slide
Rotate

Example: loading spinner

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

.spinner {
  width: 40px; height: 40px;
  border: 4px solid #eee;
  border-top-color: #534AB7;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

Example: slide in on page load

@keyframes slideUp {
  from { opacity: 0; transform: translateY(30px); }
  to   { opacity: 1; transform: translateY(0); }
}

.hero-text {
  animation: slideUp 0.6s ease-out both;
}

.hero-subtitle {
  animation: slideUp 0.6s ease-out 0.2s both; /* delayed */
}

Example: bouncing ball

@keyframes bounce {
  from { transform: translateY(0); }
  to   { transform: translateY(-40px); }
}

.ball {
  animation: bounce 0.8s cubic-bezier(0.36,0.07,0.19,0.97)
             infinite alternate;
}
alternate direction makes the animation ping-pong: it plays forward, then backward, creating a natural bounce without needing to define a return keyframe.
What does animation-fill-mode: forwards do, and when would you use it?

Visual explanations

The animation timeline

Duration: 2s
[0%]──────[25%]──────[50%]──────[75%]──────[100%]
  │           │           │           │          │
opacity:0  opacity:.5  opacity:1  opacity:.5  opacity:0
           ← defined by @keyframes at each stop →

Timing functions explained

Each ball below starts at the left and reaches the right in exactly 3 seconds — but travels differently. Watch how each timing function moves:

linear — constant speed
ease — fast start, smooth end (default)
ease-in — slow start, fast end
ease-out — fast start, slow end
ease-in-out — slow start AND slow end

When to use which timing function

FunctionBest for
linearSpinners, infinite loops, progress bars
easeGeneral transitions — feels most natural
ease-inElements that exit the screen (accelerating away)
ease-outElements that enter the screen (decelerating in)
ease-in-outAttention-grabbing effects, modal transitions
cubic-bezier()Custom physics — bounces, springs, over-shoots

Transition vs. animation: decision tree

Do you need it to run automatically (without user action)?
  YES → Use an Animation (@keyframes)
  NO  → Is the change triggered by hover/focus/class toggle?
          YES → Use a Transition
          NO  → Trigger a CSS class change with JS, then Transition

Custom cubic-bezier curves

/* Overshoot / spring effect */
animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);

/* Fast snap */
animation-timing-function: cubic-bezier(0.65, 0, 0.35, 1);

/* Use https://cubic-bezier.com to build custom curves visually */

GPU-accelerated properties (use these!)

These run on the GPU and are the fastest to animate:

Avoid animating width, height, top, left, margin, or padding — these trigger layout recalculation ("reflow") and are expensive. Use transform: translate() instead of changing top/left.

Hands-on practice

Try these in a code editor (CodePen, VS Code with Live Server). Reveal hints when you're stuck.

Beginner exercises

1Colour fade button

Create a button that transitions its background from blue to purple over 0.5s when hovered.

Use transition: background-color 0.5s ease; on the base style. Define :hover { background-color: ... }. Only the property you're changing needs to be set on :hover.
2Underline link animation

Make a nav link whose underline grows from 0 to 100% width on hover using a ::after pseudo-element.

Set ::after { content:''; display:block; height:2px; width:0; transition: width 0.3s ease; } and then on a:hover::after { width: 100%; }
3Spin on hover

Make an icon rotate 360° over 0.5s when you hover over it.

Use transition: transform 0.5s ease; on the base. Then on :hover { transform: rotate(360deg); }
4Fade-in on load

Make a heading appear by fading in from opacity 0 to 1 over 1 second when the page loads.

@keyframes fadeIn { from{opacity:0} to{opacity:1} } then .heading { animation: fadeIn 1s ease both; }
5Loading dots

Create 3 dots that fade in and out with a staggered delay to form a loading indicator.

Use @keyframes pulse that animates opacity 1→0→1. Give the 2nd dot animation-delay: 0.2s, 3rd dot 0.4s. Set animation-iteration-count: infinite.

Intermediate exercises

6Animated hamburger menu

Create a hamburger icon (3 bars) that animates into an × when a class open is toggled. The top/bottom bars rotate, the middle fades out.

Use 3 <span> inside a button. On .open span:first-child { transform: rotate(45deg) translate(5px,5px); }, middle: opacity:0, last: rotate(-45deg). Add transitions to each span.
7Card flip

Build a card that flips over on hover to reveal a back face, using perspective and rotateY.

Wrap front and back in a container with transform-style: preserve-3d. Back face gets transform: rotateY(180deg) and backface-visibility: hidden. On hover: .card { transform: rotateY(180deg); }
8Scroll reveal

Cards start invisible (opacity:0; transform: translateY(40px)). When a JS class visible is added (use IntersectionObserver), they animate in.

Add transition: opacity 0.6s ease, transform 0.6s ease; to the cards. The .visible class sets opacity:1; transform:translateY(0). Use IntersectionObserver to add the class.
9Animated progress bar

Build a progress bar that animates from 0% to a target value using @keyframes and animation-fill-mode: forwards.

@keyframes grow { from{width:0} to{width:75%} } on the inner fill bar. Use animation: grow 1.5s ease-out forwards; so it stays at 75% after finishing.
10Typewriter effect

Make text appear letter by letter using steps() timing function and animating width + overflow: hidden.

@keyframes type { from{width:0} to{width:100%} }. Apply to a fixed-width container: animation: type 3s steps(30,end) both; overflow:hidden; white-space:nowrap;. Count the characters and match the steps number.

Challenge projects

Animated landing page hero

Build a hero section where: the background gradient shifts slowly (animation), the heading slides up on load (animation), nav links underline on hover (transition), and a CTA button scales + changes colour on hover (transition). Respect prefers-reduced-motion.

Custom loading screen

Create a full-screen loading animation using only CSS. Try: a morphing blob, a bouncing logo, or staggered text letters, then fade out the screen once a class is toggled.

Common mistakes & best practices

Common mistakes

Animating layout properties — using left/top/width/height instead of transform. Causes jank and reflows.
Fix: use transform: translate() and transform: scale() instead.
Forgetting transition on the base element — putting it only on :hover means the return transition is instant.
Fix: always put transition on the base class, not the hover state.
Using transition: all carelessly — can unintentionally animate things like height: auto (which can't be transitioned).
Fix: name the specific properties you want to animate.
No animation-fill-mode — the element snaps back to its original style the moment the animation ends.
Fix: add animation-fill-mode: both or forwards for elements that should stay put.
Animating too many elements at once — e.g. 50 elements all spinning causes dropped frames.
Fix: add will-change: transform to hint the GPU, use fewer simultaneous animations.
Ignoring prefers-reduced-motion — animations can cause nausea or headaches for users with vestibular disorders.
Fix: wrap animations in a media query (see below).

Debugging transitions

Best practices

Keep animations under 300–500ms for UI feedback. Longer than that and the UI feels sluggish. For decorative / atmospheric animations, any duration is fine.

Performance: what triggers what

Change typeCostExamples
Layout (Reflow)Expensivewidth, height, margin, top
PaintModeratecolor, background, border
CompositeCheap (GPU)transform, opacity

Accessibility: prefers-reduced-motion

/* Always include this for accessibility */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
Some users have vestibular disorders where motion on screen causes dizziness, nausea, or migraines. This media query respects their system preference to reduce motion. It's a one-liner and should be in every project.

Transitions vs. animations: when to choose what

SituationUse
Button hover stateTransition
Loading indicatorAnimation
Form field focus ringTransition
Hero text slide-in on loadAnimation
Dropdown menu open/closeTransition
Pulsing notification badgeAnimation
Card hover liftTransition
Background colour shift (ambient)Animation

Summary, revision notes & flashcards

Transitions — key points

  • Animate between 2 states (A→B)
  • Need an event trigger
  • Put transition on the base element
  • 4 sub-properties: property, duration, timing, delay
  • Cannot animate display: none

Animations — key points

  • Run automatically (no trigger needed)
  • Define with @keyframes
  • Can loop, reverse, alternate
  • 8 sub-properties
  • Use fill-mode: both to prevent snap-back

Performance rules

  • Animate transform + opacity
  • Avoid animating layout properties
  • Use will-change sparingly
  • Keep UI feedback under 300ms

Accessibility

  • Always add prefers-reduced-motion
  • Don't rely on animation to convey info
  • Ensure focus states are visible
  • Avoid flashing > 3 times/sec

Flashcards — tap to flip

Tap any card to reveal the answer.

What triggers a CSS transition?
A state change — usually :hover, :focus, :active, or a class toggled by JavaScript.
What does @keyframes define?
The style at each step of a CSS animation — using percentages (0%, 50%, 100%) or from/to.
Which two properties are GPU-accelerated?
transform and opacity — they composite without triggering layout or paint recalculation.
What does animation-fill-mode: forwards do?
Keeps the element at the last keyframe state after the animation finishes, instead of snapping back.
Transition shorthand order?
property → duration → timing-function → delay
e.g. transition: color 0.3s ease 0s
What does animation-direction: alternate do?
Makes the animation play forward, then backward on each repeat — creating a smooth ping-pong effect.
What media query respects reduced-motion preferences?
@media (prefers-reduced-motion: reduce) — wrap all animations and transitions inside to disable them for users who need it.
When should you use animation vs. transition?
Transition: for state changes triggered by user interaction. Animation: for auto-running, looping, or multi-step sequences.

One-line revision notes

You now have everything you need to start building beautiful, accessible, performant CSS animations. The best next step: open CodePen and try the beginner exercises. Build muscle memory by writing the code yourself.