Skip to main content

Skillber v1.0 is here!

Learn more

Animations & Transitions

Checking access...

Motion enhances user experience when done right — it guides attention, provides feedback, and adds polish. CSS provides two motion systems: transitions (simple state changes) and animations (complex sequences).

CSS Transitions

Transitions smoothly change property values over time when an element changes state:

.button {
background: blue;
color: white;
padding: 12px 24px;
border: none;
border-radius: 4px;
cursor: pointer;
/* Define the transition */
transition: background-color 0.3s ease;
}
.button:hover {
background: darkblue;
}

Transition Properties

.element {
/* Shorthand: property duration timing-function delay */
transition: opacity 0.3s ease 0s;
/* Multiple properties */
transition:
opacity 0.3s ease,
transform 0.2s ease;
/* All animatable properties */
transition: all 0.3s ease;
/* Longhand */
transition-property: opacity, transform;
transition-duration: 0.3s, 0.2s;
transition-timing-function: ease, ease;
transition-delay: 0s, 0s;
}

Timing Functions

transition-timing-function: ease; /* Default: slow start, fast middle, slow end */
transition-timing-function: linear; /* Constant speed */
transition-timing-function: ease-in; /* Slow start, fast end */
transition-timing-function: ease-out; /* Fast start, slow end */
transition-timing-function: ease-in-out; /* Slow start and end */
/* Custom cubic bezier */
transition-timing-function: cubic-bezier(0.68, -0.55, 0.27, 1.55); /* Bounce effect */

Practical Transition Examples

/* Card hover: subtle lift and shadow */
.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.12);
}
/* Button hover: scale effect */
.button {
transition: transform 0.15s ease;
}
.button:hover {
transform: scale(1.05);
}
.button:active {
transform: scale(0.98);
}
/* Fade in on load */
.fade-in {
opacity: 0;
animation: fadeIn 0.5s ease forwards;
}

CSS Animations

Animations give more control than transitions — multiple steps, looping, and bidirectional control.

Keyframes

Define the animation sequence:

@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Percentage-based (more control) */
@keyframes slideIn {
0% {
opacity: 0;
transform: translateX(-100%);
}
60% {
opacity: 1;
transform: translateX(10px); /* Overshoot */
}
100% {
transform: translateX(0);
}
}

Animation Properties

.element {
/* Required: animation name and duration */
animation-name: fadeIn;
animation-duration: 0.5s;
/* Optional */
animation-timing-function: ease;
animation-delay: 0s;
animation-iteration-count: 1;
animation-direction: normal;
animation-fill-mode: none;
animation-play-state: running;
/* Shorthand */
animation: fadeIn 0.5s ease 0s 1 normal forwards;
}
PropertyValuesEffect
nameKeyframe nameWhich animation to run
duration0.5s, 2000msHow long one cycle takes
timing-functionease, linear, ease-in-outSpeed curve
delay0.5s, 1sWait before starting
iteration-count1, 3, infiniteHow many times to play
directionnormal, reverse, alternate, alternate-reversePlay direction
fill-modenone, forwards, backwards, bothStyle before/after animation
play-staterunning, pausedControl playback

Fill Modes

.element {
/* forwards: retains the final keyframe style */
animation: fadeIn 0.5s ease forwards;
/* backwards: applies initial keyframe style during delay */
animation: slideIn 0.5s ease 0.3s backwards;
}

The transform Property

Transform modifies an element’s appearance without affecting layout:

.element {
/* 2D transforms */
transform: translateX(50px); /* Move horizontally */
transform: translateY(-20px); /* Move vertically */
transform: translate(50px, -20px); /* Move both */
transform: rotate(45deg); /* Rotate clockwise */
transform: scale(1.5); /* Scale up 150% */
transform: scaleX(2); /* Stretch horizontally */
transform: skew(10deg); /* Skew */
/* 3D transforms */
transform: perspective(500px) rotateY(45deg); /* 3D card flip */
/* Multiple transforms (order matters!) */
transform: translateX(50px) rotate(45deg) scale(1.2);
/* Transform origin (default: center) */
transform-origin: top left;
transform-origin: 0 0;
}

Performance Considerations

Animate Only These Properties

For smooth 60fps animations, only animate:

/* GPU-accelerated — always smooth */
.element {
transform: ...; /* translate, scale, rotate */
opacity: ...; /* 0 to 1 */
}

These two properties are GPU-accelerated — the browser composites them without re-layout.

Avoid Animating These

/* ❌ These trigger layout recalculation — expensive */
.element {
width: ...;
height: ...;
margin: ...;
padding: ...;
top: ...;
left: ...;
}

Every time a layout-triggering property changes, the browser recalculates all element positions, which can cause jank.

will-change

Hint the browser about upcoming changes:

.element {
will-change: transform; /* Tell browser: this will animate */
}

Use sparingly — too many will-change declarations consumes memory.

prefers-reduced-motion

Respect user accessibility settings:

/* Remove all animations for users who prefer reduced motion */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}

Complete Animation Examples

Loading Spinner

@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #eee;
border-top-color: #0066cc;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}

Skeleton Loading

@keyframes shimmer {
0% { background-position: -200px 0; }
100% { background-position: calc(200px + 100%) 0; }
}
.skeleton {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200px 100%;
animation: shimmer 1.5s infinite;
height: 16px;
margin-bottom: 8px;
border-radius: 4px;
}

Page Reveal Animation

@keyframes reveal {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.reveal {
opacity: 0; /* Start hidden */
animation: reveal 0.6s ease forwards;
}
.reveal:nth-child(2) { animation-delay: 0.1s; }
.reveal:nth-child(3) { animation-delay: 0.2s; }

Try It Yourself

Create a page with:

  1. A button that smoothly changes color and scales on hover (transition)
  2. A card that lifts with shadow on hover (transition, transform)
  3. A loading spinner (keyframe animation, infinite)
  4. Elements that fade in one after another (animation with delays)
  5. A notification that slides in, pauses, then slides out (keyframes with percentage stops)
  6. Test with prefers-reduced-motion: reduce in Chrome DevTools

Key Takeaways

  • Use transition for simple state changes (hover, active, focus)
  • Use @keyframes + animation for complex multi-step sequences
  • Only animate transform and opacity for smooth 60fps performance
  • Avoid animating layout properties (width, height, margin, padding, top, left)
  • animation-fill-mode: forwards retains the final state after animation ends
  • Always include prefers-reduced-motion: reduce for accessibility
  • transform: translate() is for positioning, not top/left
  • Use animation-delay to create staggered reveal sequences