CSS Transitions — Animating State Changes
Without transitions, every CSS state change is instantaneous — a button changes color the moment your cursor touches it, a card jumps to its hovered position with no movement. With transitions, that change animates smoothly over time. The difference between a site that feels polished and one that feels abrupt is often a single CSS property.
How transitions work
Section titled “How transitions work”A CSS transition watches a property for changes and animates the shift from one value to another. The animation is triggered by a state change — most commonly :hover, but also :focus, :active, or a class being toggled by JavaScript.
.btn { background-color: #2c4a1e; transition: background-color 200ms ease;}
.btn:hover { background-color: #3a5c26;}When the cursor enters the button, the background color animates from #2c4a1e to #3a5c26 over 200 milliseconds. When the cursor leaves, it animates back.
The four transition sub-properties
Section titled “The four transition sub-properties”transition-property
Section titled “transition-property”Which CSS property to animate. Use specific property names rather than all:
transition-property: background-color; /* only animate this */transition-property: all; /* animate everything — avoid this */all is tempting but problematic — it animates every property that changes on that element, including properties you did not intend to animate, and can cause unexpected jank. Target specific properties instead.
transition-duration
Section titled “transition-duration”How long the animation takes. Use milliseconds (ms) or seconds (s):
transition-duration: 150ms; /* fast — micro-interactions, buttons */transition-duration: 250ms; /* medium — card hover effects */transition-duration: 400ms; /* slow — use sparingly */For UI interactions, stay between 100ms and 400ms. Below 100ms is imperceptible; above 400ms feels sluggish.
transition-timing-function
Section titled “transition-timing-function”The acceleration curve — how the animation speeds up and slows down across its duration:
ease(default) — starts slow, speeds up through the middle, ends slow. Feels natural for most interactions.linear— constant speed throughout. Feels mechanical. Good for loading spinners.ease-in— starts slow, ends fast. Good for elements leaving the screen.ease-out— starts fast, ends slow. Good for elements entering the screen.ease-in-out— slow at both ends. Smooth and deliberate.
For hover effects, ease is almost always the right choice.
transition-delay
Section titled “transition-delay”How long to wait before the animation begins:
transition-delay: 100ms; /* wait 100ms, then start the animation */Useful for staggered sequences but rarely needed for basic hover interactions.
The transition shorthand
Section titled “The transition shorthand”Write all four values in a single declaration: property duration timing-function delay:
.tour-card { transition: transform 250ms ease;}For multiple properties, comma-separate them:
.tour-card { transition: transform 250ms ease, box-shadow 250ms ease;}The critical rule: transition on the default state
Section titled “The critical rule: transition on the default state”This is the single most common transition mistake. If you put transition on the :hover rule instead of the default rule, the animation only plays when entering the hover state — it snaps back instantly on mouse-out:
/* WRONG — only animates in, snaps out */.btn:hover { background-color: #3a5c26; transition: background-color 200ms ease;}
/* CORRECT — animates in both directions */.btn { background-color: #2c4a1e; transition: background-color 200ms ease; /* on the default state */}
.btn:hover { background-color: #3a5c26;}The transition on the default state applies every time that property changes — entering hover and leaving it. Always define transitions here.
Using transform for movement and scale
Section titled “Using transform for movement and scale”transform is the preferred property for animating position and size. The browser renders transform animations using the GPU — it does not recalculate layout on every frame. This keeps animations smooth even on complex pages.
Two transforms used throughout the STO site:
transform: translateY(-6px); /* moves the element 6px upward */transform: scale(1.04); /* scales the element to 104% */Contrast this with animating layout properties:
/* Avoid — causes layout recalculation on every frame */.tour-card:hover { top: -6px; /* requires position: relative and triggers layout */}
/* Preferred — GPU composited, no layout cost */.tour-card:hover { transform: translateY(-6px);}STO card and button transitions
Section titled “STO card and button transitions”Tour card hover lift:
.tour-card { transition: transform 250ms ease, box-shadow 250ms ease;}
.tour-card:hover { transform: translateY(-6px); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.16);}Button hover:
.btn { transition: transform 150ms ease, box-shadow 150ms ease;}
.btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);}Buttons use a shorter duration than cards — 150ms versus 250ms — because buttons are small and a faster response feels more immediate.
Nav link underline reveal:
A classic pattern: reveal an underline on hover using a ::after pseudo-element that grows from width: 0 to width: 100%:
.nav-link { position: relative; text-decoration: none; color: #f5f0eb;}
.nav-link::after { content: ""; position: absolute; bottom: -4px; left: 0; width: 0; height: 2px; background-color: #a8c97f; transition: width 200ms ease; /* transition on the default state */}
.nav-link:hover::after { width: 100%;}Note that the transition is on the default .nav-link::after rule, not on .nav-link:hover::after — the same rule that applies to mouse-out applies to mouse-in.
Accessibility note
Section titled “Accessibility note”Some users experience discomfort or motion sickness from animation. In Module 07 (Responsive Design) you will learn the prefers-reduced-motion media query, which lets users opt out of animation at the OS level. Keep transitions brief and purposeful — they should enhance usability, not distract from it.
Exercise
Section titled “Exercise”Add transitions to the STO site:
- Add a card hover transition to
style.css:
.tour-card { transition: transform 250ms ease, box-shadow 250ms ease;}
.tour-card:hover { transform: translateY(-6px); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.16);}- Add a button transition:
.btn { transition: transform 150ms ease, box-shadow 150ms ease;}
.btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);}-
Open the browser. Hover over a tour card — it should lift smoothly. Move the cursor off quickly — it should animate back, not snap. This confirms the transition is defined on the default state.
-
Open DevTools. While hovering a card, watch the Computed tab — the
transformvalue updates in real time. In Chrome or Edge, open More Tools → Animations to see the transition timeline. -
Change the card transition from
250ms easeto1000ms linear. Hover the card — it should feel slow and mechanical. Revert to250ms easeand notice the difference.
transitionanimates the change between two CSS property values, triggered by state changes like:hoverand:focus.- Sub-properties:
transition-property(what),transition-duration(how long),transition-timing-function(pace curve),transition-delay(wait before start). - Always define
transitionon the default state, not on the:hoverrule — this ensures animation plays in both directions. - Use comma-separated transitions for multiple properties:
transition: transform 250ms ease, box-shadow 250ms ease. transform(translateY, scale) is preferred over top/left/width/height for animation — it is GPU-accelerated and does not trigger layout recalculation.- Timing functions:
easefeels natural,linearfeels mechanical,ease-outenters smoothly,ease-inexits smoothly.
Module 05 introduces Flexbox — the layout system that controls how elements are arranged in a row or column on the page.