036: 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.
.card {
transform: translateY(-4px);
}
Common functions:
transform: translate(1rem, -0.5rem);
transform: scale(1.05);
transform: rotate(3deg);
transform: skewX(-5deg);
Multiple functions compose:
.icon {
transform: translateX(0.25rem) rotate(8deg);
}
Order can matter because transforms are applied in sequence.
Transform origin
.menu-icon {
transform-origin: center;
}
Another example:
.badge {
transform-origin: left center;
transform: scale(1.1);
}
Important layout distinction
.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
.button {
background: #2563eb;
transition: background-color 160ms ease;
}
.button:hover {
background: #1d4ed8;
}
Longhand properties:
.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:
transition: all 200ms ease;
all can animate properties you did not intend after future CSS changes.
Easing
Common timing functions:
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
.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
@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:
animation:
pulse
1.2s
ease-in-out
0s
infinite
normal
both;
Important subproperties include:
animation-nameanimation-durationanimation-timing-functionanimation-delayanimation-iteration-countanimation-directionanimation-fill-modeanimation-play-state
Worked example: enter animation that remains optional
@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:
@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:
.drawer {
left: -20rem;
transition: left 300ms;
}
Often better:
.drawer {
transform: translateX(-100%);
transition: transform 300ms ease;
}
Still test actual performance and accessibility.
Reduced motion
@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
<div class="loading" role="status">
<span class="spinner" aria-hidden="true"></span>
Loading orders…
</div>
.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:
/* often more expensive for motion */
.panel {
transition: inline-size 300ms;
}
Transforms and opacity are often easier for browsers to composite efficiently:
.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
.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:
.button {
transition: all 200ms ease;
}
all can accidentally animate a future property that should change immediately.
Prefer:
.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.
.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
@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
@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:
- confirm the property has a previous computed value;
- confirm the before/after values are animatable;
- check whether the element was just inserted or changed from
display: none; - inspect
transition-property; - check reduced-motion rules;
- 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
- Build a button with targeted transition properties.
- Compare transform movement with
position: relative; top. - Create a one-time notification enter animation.
- Add a reduced-motion alternative.
- Build a spinner with visible text status.
- 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
-
MDN:
@starting-style— https://developer.mozilla.org/en-US/docs/Web/CSS/@starting-style -
MDN: Using CSS transitions — https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_transitions/Using_CSS_transitions
-
MDN: Transform — https://developer.mozilla.org/en-US/docs/Web/CSS/transform
-
MDN: CSS transitions — https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_transitions
-
MDN: CSS animations — https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animations
-
MDN:
prefers-reduced-motion— https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion
