Module: CSS
CSS·036·4 MIN READ

036: Transforms, Transitions, and Keyframe Animations

TOPICS COVERED: Transforms, Transitions, and Keyframe Animations

Learning outcomes

By the end, you can transform elements without confusing layout position; build targeted transitions; understand easing and duration; create keyframe animations; choose properties that animate well; respect prefers-reduced-motion; and debug motion that causes layout, paint, or accessibility problems.

Prerequisites and retrieval

Retrieve positioning from 026 and interaction states from 033. Motion should enhance a usable static interface.

Transforms

Transforms alter an element's rendered coordinate system.

css
.card {
  transform: translateY(-4px);
}

Common functions:

css
transform: translate(1rem, -0.5rem);
transform: scale(1.05);
transform: rotate(3deg);
transform: skewX(-5deg);

Multiple functions compose:

css
.icon {
  transform: translateX(0.25rem) rotate(8deg);
}

Order can matter because transforms are applied in sequence.

Transform origin

css
.menu-icon {
  transform-origin: center;
}

Another example:

css
.badge {
  transform-origin: left center;
  transform: scale(1.1);
}

Important layout distinction

css
.card {
  transform: translateY(-20px);
}

visually moves the card, but surrounding layout still reserves its original position. If you need other boxes to reflow, change layout properties instead of using transform as a layout hack.

Transitions

css
.button {
  background: #2563eb;
  transition: background-color 160ms ease;
}

.button:hover {
  background: #1d4ed8;
}

Longhand properties:

css
.button {
  transition-property: background-color, transform;
  transition-duration: 160ms, 160ms;
  transition-timing-function: ease, ease;
  transition-delay: 0ms, 0ms;
}

Prefer a list of intended properties over:

css
transition: all 200ms ease;

all can animate properties you did not intend after future CSS changes.

Easing

Common timing functions:

css
transition-timing-function: linear;
transition-timing-function: ease;
transition-timing-function: ease-in;
transition-timing-function: ease-out;
transition-timing-function: cubic-bezier(.2, .8, .2, 1);

Use easing to communicate physical/interaction intent. Tiny interface feedback often feels good with a short ease-out.

Worked example: button interaction

css
.button {
  transform: translateY(0);
  background: #2563eb;
  box-shadow: 0 2px 4px rgb(15 23 42 / 0.12);
  transition:
    transform 140ms ease-out,
    background-color 140ms ease-out,
    box-shadow 140ms ease-out;
}

.button:hover {
  background: #1d4ed8;
  transform: translateY(-1px);
  box-shadow: 0 5px 14px rgb(15 23 42 / 0.16);
}

.button:active {
  transform: translateY(0);
}

.button:focus-visible {
  outline: 3px solid #f59e0b;
  outline-offset: 3px;
}

The focus indicator is not replaced by motion.

Keyframe animations

css
@keyframes pulse {
  0% {
    transform: scale(1);
  }

  50% {
    transform: scale(1.04);
  }

  100% {
    transform: scale(1);
  }
}

.status-dot {
  animation: pulse 1.2s ease-in-out infinite;
}

Shorthand:

css
animation:
  pulse
  1.2s
  ease-in-out
  0s
  infinite
  normal
  both;

Important subproperties include:

  • animation-name
  • animation-duration
  • animation-timing-function
  • animation-delay
  • animation-iteration-count
  • animation-direction
  • animation-fill-mode
  • animation-play-state

Worked example: enter animation that remains optional

css
@keyframes fade-rise {
  from {
    opacity: 0;
    transform: translateY(0.5rem);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.notice {
  animation: fade-rise 220ms ease-out both;
}

Reduced motion:

css
@media (prefers-reduced-motion: reduce) {
  .notice {
    animation: none;
  }
}

The content is immediately visible without animation.

Motion and performance

Transforms and opacity are often efficient choices for visual motion because they may avoid repeated layout, but this is not an absolute guarantee. Browser rendering depends on context.

Avoid animating layout-heavy properties such as width, height, or top/left on large complex regions when a transform can express equivalent decorative motion.

Bad pattern:

css
.drawer {
  left: -20rem;
  transition: left 300ms;
}

Often better:

css
.drawer {
  transform: translateX(-100%);
  transition: transform 300ms ease;
}

Still test actual performance and accessibility.

Reduced motion

css
@media (prefers-reduced-motion: reduce) {
  .parallax,
  .auto-rotating-carousel,
  .decorative-spinner {
    animation: none;
    transform: none;
  }
}

Do not remove an essential progress indicator without replacing its meaning. Reduced motion means reduce unnecessary movement, not hide system status.

Worked example: loading indicator with accessible status

html
<div class="loading" role="status">
  <span class="spinner" aria-hidden="true"></span>
  Loading orders…
</div>
css
.spinner {
  inline-size: 1rem;
  aspect-ratio: 1;
  border: 2px solid #cbd5e1;
  border-block-start-color: #2563eb;
  border-radius: 50%;
  animation: spin 700ms linear infinite;
}

@keyframes spin {
  to {
    transform: rotate(1turn);
  }
}

@media (prefers-reduced-motion: reduce) {
  .spinner {
    animation: none;
    border-block-start-color: #2563eb;
  }
}

The textual status remains regardless of motion.

Deep dive: animation pipeline and compositing

Not every animated property has the same cost.

Changes to layout properties can trigger layout and paint:

css
/* often more expensive for motion */
.panel {
  transition: inline-size 300ms;
}

Transforms and opacity are often easier for browsers to composite efficiently:

css
.panel {
  transition:
    transform 180ms ease,
    opacity 180ms ease;
}

This is a performance guideline, not a law. Measure real pages instead of assuming every transform is free.

3D transforms

css
.scene {
  perspective: 800px;
}

.card {
  transform: rotateY(12deg) translateZ(20px);
  transform-style: preserve-3d;
}

3D transforms change coordinate systems and stacking behavior. Use them sparingly for interfaces; readability and pointer targets are more important than spectacle.

Transition only what you intend

Avoid:

css
.button {
  transition: all 200ms ease;
}

all can accidentally animate a future property that should change immediately.

Prefer:

css
.button {
  transition:
    background-color 160ms ease,
    color 160ms ease,
    transform 120ms ease;
}

Entry/exit transitions and discrete properties

Modern CSS can transition some previously awkward entry/exit cases with @starting-style and transition-behavior: allow-discrete.

css
.popover-panel {
  opacity: 1;
  transform: translateY(0);
  transition:
    opacity 160ms,
    transform 160ms,
    display 160ms allow-discrete;
}

@starting-style {
  .popover-panel:popover-open {
    opacity: 0;
    transform: translateY(-0.4rem);
  }
}

Support and exact behavior should be checked against the project's browser baseline. Build a usable non-animated state first.

Keyframe control

css
@keyframes pulse {
  0%, 100% { opacity: 0.6; }
  50% { opacity: 1; }
}

.sync-indicator {
  animation:
    pulse 1.2s ease-in-out infinite;
}

Important controls include:

  • animation-duration;
  • animation-delay;
  • animation-iteration-count;
  • animation-direction;
  • animation-fill-mode;
  • animation-play-state;
  • animation-timing-function.

Do not use infinite animation for decoration that continuously demands attention.

Reduced motion is a design branch

css
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    scroll-behavior: auto;
  }

  .sync-indicator {
    animation: none;
  }

  .panel {
    transition-duration: 0.01ms;
  }
}

A blanket reset can be useful in controlled projects, but component-specific alternatives are often better. Some users tolerate fades but not large movement. Preserve state change even when motion is removed.

Motion debugging

When a transition does not run:

  1. confirm the property has a previous computed value;
  2. confirm the before/after values are animatable;
  3. check whether the element was just inserted or changed from display: none;
  4. inspect transition-property;
  5. check reduced-motion rules;
  6. inspect whether JavaScript changes state twice in the same rendering frame.

Understanding the lifecycle is more reliable than repeatedly increasing duration.

Common mistakes

  • transition: all.
  • Animating every hoverable component.
  • Long entrance animations that delay access to content.
  • Parallax or large continuous movement without reduced-motion handling.
  • Using transform for actual page layout.
  • Forgetting animation can create stacking contexts.
  • Infinite animation drawing attention to non-essential decoration.
  • Relying on movement as the only status change.

Practice set

  1. Build a button with targeted transition properties.
  2. Compare transform movement with position: relative; top.
  3. Create a one-time notification enter animation.
  4. Add a reduced-motion alternative.
  5. Build a spinner with visible text status.
  6. Use DevTools animation tools to inspect duration and easing.

Recap

Transforms change rendered geometry, transitions interpolate between property states, and keyframes describe multi-step animations. Motion is strongest when brief, purposeful, optional, and compatible with user preferences.

Official references