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.

Interactive demo​

Toggle a card state and compare durations and easing curves:

Transition playground

Toggle the card state and feel how duration and easing change the motion.

Hover-like state

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. translateX shifts along the x-axis, translateY along the y-axis, and translate(x, y) moves in both directions at once:

.box-a:hover {
transform: translateX(20px);
}

.box-b:hover {
transform: translateY(-10px);
}

.box-c:hover {
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​

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

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

This is the CSS half of a scroll-reveal pattern. In practice, the .visible class is usually added by JavaScript (for example with IntersectionObserver) or by newer scroll-driven animation APIs.

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.