Chapter 16 of 17 · CSS for Beginners — Complete Free Tutorial

Transitions, Transforms & Animations

Motion explains change: a button presses, a panel slides in, a spinner turns. CSS can do all of it natively — with near-zero JavaScript and excellent performance — as long as you animate the right properties and respect users who prefer less motion.

Transitions: smooth state changes

A transition interpolates a property between two states (usually rest → hover):

.btn {
  background: #4f46e5;
  color: white;
  padding: 0.7rem 1.4rem;
  border-radius: 0.6rem;
  transition: background-color 0.2s ease, transform 0.15s ease;
}
.btn:hover  { background: #4338ca; transform: translateY(-2px); }
.btn:active { transform: translateY(0); }

The four transition properties

PropertyMeaning
transition-propertyWhich property to animate (all works but is blunt)
transition-durationHow long — 0.15–0.3s feels responsive
transition-timing-functionThe easing: ease, ease-in-out, cubic-bezier(.2,.8,.2,1), linear
transition-delayWait before starting

Shorthand order: property duration timing delay.

Expected output: hovering the button tints it darker and floats it up 2 px over 150–200 ms — noticeably premium versus an instant colour snap.

Transforms: move, rotate, scale

.avatar {
  transition: transform 0.25s ease;
}
.avatar:hover { transform: scale(1.08); }

.menu-item:hover { transform: translateX(6px); }        /* shift right */
.badge { transform: rotate(-6deg); }                    /* tilt */
.ribbon {
  transform: rotate(45deg) scale(1.1);
  transform-origin: center;                            /* pivot point */
}

@keyframes wobble {
  0%, 100% { transform: rotate(-3deg); }
  50%      { transform: rotate(3deg); }
}

Key points:

  • Transforms do not affect layout — neighbours never shift (that is why they animate so smoothly).
  • Order matters: rotate(45deg) scale(1.1) ≠ scale(1.1) rotate(45deg).
  • translate() accepts percentages of the element's own size (-50% centring classic).
  • transform-origin sets the pivot (default: centre).

Animations: @keyframes

When one state is not enough, define a timeline:

@keyframes slide-in {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: translateY(0); }
}

.toast {
  animation: slide-in 0.35s cubic-bezier(0.2, 0.8, 0.2, 1) both;
  /* both = apply the starting state during delay, keep the end state after */
}

.spinner {
  width: 2rem;
  height: 2rem;
  border: 3px solid #e2e8f0;
  border-top-color: #4f46e5;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

Animation shorthand

animation:
  name duration timing-function delay
  iteration-count direction fill-mode state;
ValueOptions
iteration-count3, infinite
directionnormal, reverse, alternate (ping-pong)
fill-modenone, forwards, both
play-staterunning, paused

Transition vs animation

transitionanimation (@keyframes)
TriggerA state change (hover, class, attribute)Runs on demand / loop
Keyframe stepsTwo (before/after)Many (0%, 25%, 50%…)
LoopingNoYes
Typical useButtons, hovers, revealsSpinners, entrances, marquees

Staggered reveals with Intersection Observer

.reveal { opacity: 0; transform: translateY(24px); transition: opacity .6s ease, transform .6s ease; }
.reveal.is-visible { opacity: 1; transform: none; }
const io = new IntersectionObserver((entries) => {
  entries.forEach((e, i) => {
    if (e.isIntersecting) {
      e.target.style.transitionDelay = `${i * 80}ms`;   // stagger
      e.target.classList.add('is-visible');
      io.unobserve(e.target);
    }
  });
}, { threshold: 0.2 });
document.querySelectorAll('.reveal').forEach((el) => io.observe(el));

Expected output: cards fade-and-rise one after another as they scroll into view.

Respect reduced motion

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

This is not optional polish — vestibular disorders make sweeping motion physically unpleasant.

Performance tips

  • Animate transform and opacity only — they are composited on the GPU without re-layout/repaint.
  • Avoid animating width, height, top/left, box-shadow on large areas (layout/paint cost).
  • Keep durations short (≤ 300 ms for UI feedback); longer feels sluggish.
  • Use will-change: transform sparingly, on elements that are about to animate — never on everything.

Common mistakes

  • transition: all — surprises (including unintended properties) and worse performance; list properties explicitly.
  • Animating layout properties — jank on slow devices.
  • No prefers-reduced-motion query — an accessibility regression.
  • Entrance animations that delay reading — content should be usable immediately; reveals must be fast and optional.

Practice checklist

  1. Add a hover lift + shadow transition to a card.
  2. Build a CSS-only spinner with @keyframes spin.
  3. Wrap your animations in a prefers-reduced-motion override and verify it in dev tools (Rendering panel → emulate).

One topic remains that ties your whole stylesheet together — CSS Variables & Specificity.

Quiz: Transitions, Transforms & Animations

5 questions · 70% to pass

Frequently Asked Questions

CSS (Cascading Style Sheets) is the language that controls how HTML content looks — colours, fonts, spacing, layout, responsiveness and animation. HTML provides structure and meaning; CSS provides presentation. One stylesheet can restyle an entire website at once.

The fundamentals (selectors, box model, Flexbox, media queries) take about 2–3 weeks of daily practice. Grid, animations and architecture come with a little more practice. This 17-chapter series is designed for 3–4 weeks at a relaxed pace — faster if you type out every example.

Yes. HTML first, then CSS, then JavaScript. CSS and HTML together let you build complete, good-looking static pages, and almost every JavaScript framework assumes you already know CSS layout and selectors.

No — CSS is a styling language with selectors and property-value declarations, not variables, loops or conditionals. You can get far without any programming logic, and JavaScript adds the behaviour later.

Flexbox distributes space along one axis (a row or a column) and is ideal for components — toolbars, navbars, card rows. Grid controls rows and columns together and is ideal for page structure and card grids. They are complementary: many layouts use a grid page shell with flex components inside.

Usually one of three reasons: the selector does not match (check class names), a more specific rule wins (use the browser inspector to see computed styles and specificity), or the file is not linked/loads in the wrong order. The specificity chapter explains how the cascade decides.

Every element is a box of four layers: content, padding (inner space), border, and margin (outer space). With box-sizing: border-box, the width you set includes padding and border — the universal reset every project uses.

Use relative units, flexible grids like repeat(auto-fit, minmax(260px, 1fr)), images with max-width: 100%, and mobile-first media queries. The goal is zero horizontal scrolling from 320px up — the responsive chapters show the full workflow.

Not to learn. Frameworks are productivity tools built on the same CSS you are learning here; understanding the fundamentals makes any framework easier to use (and to debug when it fights you). Learn vanilla CSS first, then evaluate frameworks with an informed eye.

JavaScript for interactivity, then a build tool and version control (Git). With HTML, CSS and JavaScript you can build complete websites; after that, frameworks like React or server-side tools like Laravel Blade build on these fundamentals.