Skip to main content

Transitions & Animations

Transitions smoothly animate CSS property changes. Keyframe animations create more complex, multi-step sequences. Both make interfaces feel responsive and polished.

Transitions

A transition tells the browser: "When this property changes, animate the change over time instead of switching instantly."

The transition property

.button {
background-color: #4a90d9;
color: white;
padding: 12px 24px;
border: none;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.2s ease;
}

.button:hover {
background-color: #2d6cb4;
}

Without the transition, the colour would change instantly on hover. With it, the colour fades smoothly over 0.2 seconds.

Transition syntax

transition: property duration timing-function delay;
PartExampleWhat it does
propertybackground-colorWhich CSS property to animate
duration0.2sHow long the animation takes
timing-functioneaseThe acceleration curve
delay0.1sWait before starting (optional)

Transitioning multiple properties

List them separated by commas:

.card {
transition:
transform 0.2s ease,
box-shadow 0.2s ease;
}

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

Or use all to transition every property that changes:

.card {
transition: all 0.2s ease;
}

Note: transition: all is convenient but can cause unintended animations (e.g., width changes during layout shifts). Prefer listing specific properties.

Timing functions

The timing function controls the animation's acceleration curve:

ValueBehaviour
ease (default)Slow start, fast middle, slow end
linearConstant speed throughout
ease-inSlow start, fast end
ease-outFast start, slow end
ease-in-outSlow start and end, fast middle
cubic-bezier(x,y,x,y)Custom curve

For UI interactions, ease or ease-out feel the most natural. linear is good for continuous animations like progress bars.

Tip: Keep transition durations short -- 0.15s to 0.3s for hover effects. Longer durations (0.3s--0.5s) work for larger movements like panels sliding in.

Which properties can be transitioned?

Not every CSS property can be smoothly animated. Properties that can be transitioned include:

  • Colour properties: color, background-color, border-color
  • Size properties: width, height, padding, margin, font-size
  • Transform properties: transform (translate, scale, rotate)
  • Opacity: opacity
  • Shadow: box-shadow, text-shadow

Properties that cannot be transitioned: display, font-family, background-image, position.

CSS transforms

Transforms change an element's position, size, or rotation without affecting the layout around it. Other elements behave as if the transform did not happen.

translate

Moves an element:

.box:hover {
transform: translateX(20px);
transform: translateY(-10px);
transform: translate(20px, -10px);
}

scale

Resizes an element:

.box:hover {
transform: scale(1.1);
}

.box:active {
transform: scale(0.95);
}

scale(1.1) makes the element 10% larger. scale(0.95) makes it 5% smaller. You can scale axes independently: scaleX(1.5), scaleY(0.8).

rotate

Rotates an element:

.icon:hover {
transform: rotate(45deg);
}

Positive values rotate clockwise; negative values rotate counter-clockwise.

skew

Slants an element:

.slanted {
transform: skewX(-5deg);
}

Combining transforms

Chain multiple transforms in one declaration:

.card:hover {
transform: translateY(-4px) scale(1.02) rotate(1deg);
}

Order matters -- transforms are applied right to left.

transform-origin

By default, transforms originate from the element's centre. Change the origin point:

.box {
transform-origin: top left;
}

.box:hover {
transform: rotate(10deg);
}

The element rotates around its top-left corner instead of its centre.

Keyframe animations

Transitions animate between two states (e.g., hover on/off). Keyframe animations can have multiple steps and run automatically without user interaction.

Defining keyframes

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

Or use percentages for multi-step animations:

@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}

Applying animations

.card {
animation: fadeIn 0.5s ease;
}

The animation shorthand

animation: name duration timing-function delay iteration-count direction fill-mode;
PartExampleWhat it does
namefadeInReferences the @keyframes name
duration0.5sHow long one cycle takes
timing-functioneaseAcceleration curve
delay0.2sWait before starting
iteration-countinfiniteHow many times to repeat (default: 1)
directionalternatePlay forward, backward, or both
fill-modeforwardsWhat state to keep after the animation ends

animation-iteration-count

.spinner {
animation: spin 1s linear infinite;
}

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

infinite loops forever. A number like 3 plays three times and stops.

animation-direction

ValueBehaviour
normalPlays forward each cycle
reversePlays backward each cycle
alternateForward, then backward, then forward...
alternate-reverseBackward, then forward, then backward...
.bounce {
animation: bounce 1s ease-in-out infinite alternate;
}

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

animation-fill-mode

Controls the element's style before and after the animation:

ValueBehaviour
noneNo styles applied outside animation (default)
forwardsKeeps the final keyframe styles after animation ends
backwardsApplies the first keyframe styles during the delay
bothCombines forwards and backwards
.fade-in {
opacity: 0;
animation: fadeIn 0.5s ease 0.2s forwards;
}

Without forwards, the element would snap back to opacity: 0 after the animation finishes.

animation-play-state

Pause and resume animations:

.animation {
animation: spin 2s linear infinite;
}

.animation:hover {
animation-play-state: paused;
}

Performance considerations

Not all CSS properties are equally cheap to animate. The browser handles layout changes (width, height, margin) by recalculating positions of surrounding elements. This is expensive.

The safe list

These properties are cheap to animate because they skip layout and paint:

  • transform (translate, scale, rotate)
  • opacity

Avoid animating: width, height, top, left, margin, padding, border-width.

Instead of animating top:

/* Slow */
.box {
position: relative;
top: 0;
transition: top 0.2s;
}
.box:hover {
top: -10px;
}

/* Fast */
.box {
transition: transform 0.2s;
}
.box:hover {
transform: translateY(-10px);
}

will-change

Hints to the browser that an element will be animated, so it can optimise ahead of time:

.card {
will-change: transform;
transition: transform 0.2s ease;
}

Note: Only use will-change on elements that will actually be animated. Overusing it wastes GPU memory.

Respecting user preferences

Some users experience motion sickness from animations. Respect their preference:

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

This effectively disables all animations for users who have turned on the "reduce motion" setting in their operating system.

Common animation patterns

Fade in on scroll (CSS only)

.fade-in {
opacity: 0;
transform: translateY(30px);
transition: opacity 0.6s ease, transform 0.6s ease;
}

.fade-in.visible {
opacity: 1;
transform: translateY(0);
}

The .visible class is typically added by JavaScript when the element enters the viewport.

Button press effect

.button {
transition: transform 0.1s ease;
}

.button:hover {
transform: translateY(-2px);
}

.button:active {
transform: translateY(1px);
}

Loading spinner

.spinner {
width: 40px;
height: 40px;
border: 4px solid #e0e0e0;
border-top-color: #4a90d9;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}

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

What you learned

  • Transitions smoothly animate property changes between two states
  • The transition shorthand takes property, duration, timing-function, and delay
  • Transforms (translate, scale, rotate, skew) change appearance without affecting layout
  • Keyframe animations define multi-step sequences with @keyframes and the animation property
  • animation-fill-mode: forwards keeps the end state; infinite loops forever
  • Animate only transform and opacity for best performance
  • Always respect prefers-reduced-motion for accessibility

Next step

Transitions and transforms make your interfaces dynamic. The next chapter covers pseudo-classes and pseudo-elements -- special selectors that target element states and create virtual elements.