CSS Keyframe Animations
What You Learn From This Page
- How
@keyframesdefines two-stage and multi-stage animation sequences. - How the individual animation properties and shorthand control playback.
- How duration, delay, iteration count, direction, fill mode, and play state interact.
- How percentages, missing endpoints, duplicate offsets, and segment easing are resolved.
- When keyframes are more appropriate than transitions and when they are unnecessary.
- How to build entrances, spinners, progress indicators, staggered lists, and feedback effects.
- How to keep animation performant, accessible, interruptible, and consistent.
- How to diagnose name, duration, cascade, lifecycle, and rendering problems.
Introduction
CSS keyframe animations describe a sequence of property values that the browser plays over time. A transition normally interpolates when a property changes from one state to another. A keyframe animation can start when it is applied, repeat without another state change, move through several intermediate stages, reverse direction, pause, and retain selected endpoint styles. This makes keyframes suitable for spinners, progress effects, notification entrances, controlled attention cues, and other motion that needs more than a simple state-to-state change.
The sequence and its playback controls are separate. An @keyframes rule defines what values occur at particular points. Properties on the element define which sequence runs, how long one cycle lasts, how progress is eased, when playback begins, how many cycles occur, which direction each cycle follows, and what effect applies before and after active playback.
Good animation is not simply motion that works. It communicates state or continuity without delaying the task, consuming unnecessary resources, or causing discomfort. The keyframes should be understandable, the normal component state should remain explicit, and reduced-motion users should receive a usable alternative. This tutorial develops that production model from syntax through lifecycle, accessibility, performance, and debugging.
Defining an @keyframes Rule
The @keyframes at-rule assigns a name to a collection of keyframe blocks. Each block contains a keyframe selector and the declarations that should apply at that offset. The simplest form uses from and to.
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
The rule does not run by itself. An element must reference the name and have a duration greater than zero.
.message {
animation-name: fade-in;
animation-duration: 400ms;
}
The browser matches animation-name to the keyframes rule, creates an animation effect, calculates values between the declared offsets, and samples that effect while it is active. Use names that describe intent, such as notification-enter, loading-spin, or success-pulse, rather than generic names such as animation1.
from, to, and Percentage Offsets
from is equivalent to 0%, and to is equivalent to 100%. Percentage selectors allow intermediate stages anywhere from the beginning to the end of one iteration.
@keyframes pulse {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.06);
}
}
The browser interpolates between declared values, so ordinary movement does not need a block at every percentage. Add an offset when the path changes direction, a property reaches a meaningful state, or a segment needs different timing. Too many unnecessary offsets make motion difficult to understand and maintain.
Multiple selectors can share one block, as the first and last offsets do above. Offsets may be written in any source order, although ascending order is easier for humans to review. The browser processes them according to their positions in the animation timeline.
Multiple Intermediate Stages
Keyframes become valuable when motion has distinct stages. The following status effect scales, changes color, pauses in its emphasized state, and then returns.
@keyframes status-confirmed {
0% {
color: inherit;
transform: scale(1);
}
35% {
color: #176b3a;
transform: scale(1.08);
}
65% {
color: #176b3a;
transform: scale(1.08);
}
100% {
color: #176b3a;
transform: scale(1);
}
}
The repeated values between 35 and 65 percent create a hold. Motion before and after that hold can use separate easing. This is clearer than attempting to coordinate several timers or chained class changes in script.
Missing Values and Underlying Styles
A keyframes rule does not have to mention every animated property at every offset. When a property is absent from an intermediate block, the browser interpolates across the surrounding keyframes that define it. If the first or last keyframe omits an animatable property, the underlying computed value can supply that endpoint.
.notice {
opacity: 1;
animation: reveal 300ms ease-out;
}
@keyframes reveal {
from { opacity: 0; }
}
Here, the normal computed opacity supplies the ending value. This can be concise, but explicitly declaring important endpoints often improves readability and protects the animation from unrelated base-style changes. Non-animatable declarations may apply discretely or have no interpolated visual path, depending on the property.
Duplicate Keyframe Offsets
When multiple keyframe blocks use the same offset within one rule, their declarations cascade together. If those blocks declare the same property, later declarations for that offset can win according to normal cascade handling within the rule. This behavior can be useful but often signals accidental duplication.
@keyframes example {
50% { opacity: 0.5; }
50% { transform: scale(1.1); }
}
At 50 percent, both opacity and transform participate. Prefer a single combined block unless generated CSS or clear separation makes duplicates useful. If multiple @keyframes rules share the same name, they do not merge as partial definitions; the applicable rule in the cascade replaces the other definition.
The Animation Longhand Properties
Eight primary longhands control CSS animation playback: animation-name, animation-duration, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, animation-fill-mode, and animation-play-state. Writing them separately is valuable while learning or debugging.
.badge {
animation-name: pulse;
animation-duration: 1.5s;
animation-timing-function: ease-in-out;
animation-delay: 0s;
animation-iteration-count: infinite;
animation-direction: normal;
animation-fill-mode: none;
animation-play-state: running;
}
Each animation property accepts comma-separated lists, which allows multiple named effects on one element. List matching can repeat shorter lists, but complete shorthand items are often easier to review.
animation-name and animation-duration
animation-name selects the keyframes rule. The special value none means no keyframe animation is selected. A misspelled or unavailable name produces no matching effect. Use consistent spelling and avoid names that are unnecessarily similar.
animation-duration defines the length of one iteration in seconds or milliseconds. Its initial value is 0s. Therefore, setting only a name can make the animation appear broken even though the rule matches; there is no visible active interval.
.alert {
animation-name: alert-enter;
animation-duration: 240ms;
}
Duration should reflect distance, complexity, and urgency. Direct interface feedback is usually short, while a larger controlled movement may need more time for continuity. Users should not wait through decorative animation before accessing normal controls.
animation-timing-function
The timing function maps elapsed time to progress between keyframe offsets. Common values include linear, ease, ease-in, ease-out, ease-in-out, cubic-bezier(), steps(), and the modern linear() function.
.spinner {
animation: spin 800ms linear infinite;
}
With intermediate keyframes, easing applies to individual segments. A timing function declared inside a keyframe controls the interval beginning at that keyframe and ending at the next one.
@keyframes bounce {
0% {
transform: translateY(0);
animation-timing-function: ease-out;
}
50% {
transform: translateY(-20px);
animation-timing-function: ease-in;
}
100% { transform: translateY(0); }
}
The function in the final keyframe has no following segment during that iteration, so it does not shape an additional interval.
animation-delay and Negative Delays
animation-delay postpones the beginning of an animation's active interval. A positive 300-millisecond delay waits before playback. Unless a backwards fill mode applies, the element shows its underlying styles during that wait.
.card {
animation: card-enter 400ms ease-out 300ms both;
}
A negative delay does not wait. It starts the animation as though the specified amount of time had already elapsed. This is useful for de-synchronizing continuous loops so repeated items do not all move in unison.
.dot:nth-child(1) { animation-delay: 0ms; }
.dot:nth-child(2) { animation-delay: -200ms; }
.dot:nth-child(3) { animation-delay: -400ms; }
Large negative values may place a finite animation near or beyond its end immediately. Test the interaction among delay, duration, iteration count, direction, and fill mode rather than treating delay as an isolated property.
animation-iteration-count
Iteration count controls how many times the sequence runs. It accepts non-negative numbers and the keyword infinite. A value of three runs three cycles. Fractional values are valid; 2.5 completes two cycles and stops halfway through the third.
.success-icon {
animation: success-pulse 260ms ease-out 2;
}
.spinner {
animation: spin 800ms linear infinite;
}
Infinite animation should communicate ongoing activity or serve a carefully chosen decorative purpose. Constant motion on many cards, labels, or background elements creates distraction, consumes power, and can make a page feel unstable.
animation-direction
Direction determines how offsets are traversed across iterations. normal runs from 0 to 100 percent every time. reverse runs from 100 to 0 percent. alternate runs forward on odd iterations and backward on even iterations. alternate-reverse starts backward and then alternates.
.floating-icon {
animation:
float 2.5s ease-in-out infinite alternate;
}
Alternation avoids a discontinuous jump from the last keyframe back to the first when an effect is naturally reversible. Direction also affects which keyframe is considered the start or end for fill behavior, so reverse modes deserve explicit testing.
animation-fill-mode
Fill mode determines whether an animation's keyframe effect applies outside its active interval. none is the initial value: underlying styles apply before and after playback. backwards applies the relevant starting keyframe during a positive delay. forwards retains the relevant ending value after the final active interval. both combines backwards and forwards behavior.
.notification {
animation:
notification-enter 240ms ease-out 200ms both;
}
Forwards fill is a visual effect, not ideal permanent application state. If a modal is open, its open class or attribute should define the stable visible state. Otherwise, removing or restarting the animation can unexpectedly reveal the underlying style. Use fill modes to support animation phases, not to replace clear component state.
animation-play-state
Play state accepts running or paused. Pausing preserves the current progress position. Returning to running resumes from that position rather than starting over.
.demo {
animation: orbit 4s linear infinite;
animation-play-state: paused;
}
.demo.is-playing {
animation-play-state: running;
}
A pause-on-hover demonstration can be useful, but hover is unavailable to many touch and keyboard users. If pausing matters, provide an explicit button whose label and pressed state communicate the current behavior.
The animation Shorthand
The shorthand can combine the playback controls into one comma-separated declaration. A common conceptual order is name, duration, timing function, delay, iteration count, direction, fill mode, and play state.
.badge {
animation:
pulse 1.5s ease-in-out 0s infinite alternate;
}
CSS grammar identifies many values by type rather than strict position, but the two time values are important: the first time is duration and the second is delay. Consistent project ordering reduces ambiguity. Shorthand declarations reset omitted animation longhands to their initial values, so adding a shorthand later can unintentionally replace a separately declared fill mode or play state.
A Production Entrance Pattern
Combining opacity with a small transform gives an entrance both visibility progression and spatial continuity. Keep the distance restrained for ordinary interface content.
@keyframes notification-enter {
from {
opacity: 0;
transform: translateY(-0.75rem);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.notification {
animation:
notification-enter 240ms ease-out both;
}
The stable component rule should still represent the final visible state. If JavaScript inserts and removes the element, coordinate removal with the exit lifecycle or use a transition for a simple open-and-closed state. Entrance animation must not postpone accessibility-tree exposure or focus management in ways that confuse assistive technology users.
Scale, Pulse, Bounce, and Shake
A small scale entrance can support a modal or popover. A pulse can acknowledge success. Bounce and shake may communicate impact or error, but they should be short, subtle, and used only when the meaning remains available through text and persistent styling.
@keyframes error-shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-4px); }
75% { transform: translateX(4px); }
}
.field-error {
animation: error-shake 240ms ease-out;
}
Do not run an error shake continuously. Trigger it once when validation occurs, keep a visible error message and invalid state, and avoid moving focus away from the field. Reapplying the same animation class may not restart playback if the computed animation has not changed; script may need to remove and reapply the class in a controlled way or use the Web Animations API for event-driven effects.
Spinners and Continuous Rotation
A spinner is a strong keyframe use case because it runs autonomously and repeats. Constant linear timing prevents a visible acceleration seam on every iteration.
@keyframes spin {
to { transform: rotate(1turn); }
}
.spinner {
animation: spin 800ms linear infinite;
}
Animation alone does not communicate loading semantics. Pair the visual indicator with appropriate status text or an accessible live region, depending on context. Stop and remove the spinner when loading ends; hidden infinite animations should not continue consuming resources unnecessarily.
Progress and Typewriter Effects
Visual progress can animate a transform from zero to full scale. Transform commonly avoids the repeated layout work caused by changing width, although the bar must have an appropriate transform origin.
@keyframes progress-fill {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
.progress__fill {
transform-origin: left;
animation: progress-fill 2s linear both;
}
Use a fixed-duration progress animation only when duration is actually known. A decorative bar that reaches the end before work finishes misleads users. Data-driven progress is often better represented by a custom property or inline value updated from real status.
A typewriter effect combines a size change with steps(). It can be decorative, but essential text should not require users to wait for every character. Screen readers may announce the complete text independently of the visual sequence, so reduced-motion behavior should reveal the finished content immediately.
Multiple Animations
One element can run several animations. Each comma-separated shorthand item forms a separate effect with its own name, duration, easing, delay, iteration, and other controls.
.illustration {
animation:
fade-in 400ms ease-out both,
float 3s ease-in-out 400ms infinite alternate;
}
The fade finishes while floating continues. Problems arise when multiple animations target the same property, such as two independent effects both writing transform. By default, replacement behavior can cause one effect to override another rather than automatically combining their transform functions. Combine transform operations in one keyframe sequence, animate wrapper and child elements separately, or deliberately use advanced composition features after checking support and requirements.
Staggered Lists
Staggering can reveal hierarchy, but long cascades make users wait. Keep the interval short, cap the number of delayed items, and avoid animating huge result sets.
.list-item {
--index: 0;
animation:
item-enter 320ms ease-out both;
animation-delay: calc(var(--index) * 60ms);
}
.list-item:nth-child(2) { --index: 1; }
.list-item:nth-child(3) { --index: 2; }
.list-item:nth-child(4) { --index: 3; }
Custom properties make the pattern scalable and allow server-rendered or scripted markup to provide an index. In reduced-motion mode, remove both animation and delay so later items do not remain hidden while waiting for an effect that no longer runs.
Page Visibility and Responsive Geometry
Browsers may reduce rendering work when a document is in a background tab, and device power-saving modes can affect frame scheduling. An animation timeline is not a reliable business timer. Do not use a five-second keyframe sequence to decide when a session expires, when data should be submitted, or when an essential process is complete. Keep application timing in appropriate program logic and treat CSS animation as presentation.
Responsive layouts also change the geometry through which an element moves. A translation that looks subtle in a wide desktop panel may cross most of a narrow mobile viewport. Prefer relative, component-aware distances and test every keyframe at supported widths, zoom levels, writing modes, and content lengths. Avoid keyframes that assume a fixed card height or a single-line label when the content can wrap or be localized.
@keyframes panel-enter {
from {
opacity: 0;
transform: translateY(min(1rem, 4vh));
}
to {
opacity: 1;
transform: translateY(0);
}
}
Viewport-relative motion should still be bounded so extreme screen dimensions do not produce extreme travel. Recheck overflow, clipping, and stacking contexts while the element is between keyframes, not only at the endpoints. Intermediate transforms can temporarily move content outside a scroll container or under another surface. Responsive animation succeeds when the same intent remains clear without assuming identical pixels everywhere.
Triggering Animations
An animation begins when its computed animation properties change from no matching animation to an active animation, commonly when an element is inserted or a class is added. Page-load animation runs when the initial style is resolved. Hover and focus selectors can also apply animations, although simple reversible feedback is usually better implemented with transitions.
.modal.is-opening {
animation: modal-enter 220ms ease-out both;
}
Removing and re-adding a class in the same rendering update may not create the style change needed to restart the effect. Avoid layout-forcing restart tricks as a general architecture. For highly interactive playback, the Web Animations API can offer explicit control while CSS keyframes remain ideal for declarative presentation.
Animation Events and Lifecycle
JavaScript can observe animationstart, animationiteration, animationend, and animationcancel. These events can coordinate cleanup or analytics, but application correctness should not depend on a decorative animation completing. Reduced motion, DOM removal, style changes, and interruption can alter the expected lifecycle.
element.addEventListener("animationend", (event) => {
if (event.animationName === "notification-exit") {
element.remove();
}
});
Filter by animation name when several effects run on the same element. Include a deterministic fallback for essential cleanup. If the animation is removed before completion, an end event may not represent the path you expected; cancellation should be considered explicitly.
Transitions Versus Keyframe Animations
| Capability | Transition | Keyframe Animation |
|---|---|---|
| Typical trigger | Computed value changes | Animation is applied |
| Intermediate stages | Usually two endpoints | Any number of offsets |
| Automatic repetition | No | Finite or infinite iterations |
| Direction controls | Follows state change | Normal, reverse, and alternate modes |
| Simple hover or open state | Usually ideal | Often unnecessary |
| Spinner or multi-stage sequence | Poor fit | Strong fit |
Choose the simpler mechanism that expresses the state. A button hover, accordion opening, or color change between known states usually needs a transition. A spinner, repeated pulse, sprite sequence, or choreographed multi-stage entrance benefits from keyframes. Using animation for every hover creates restart and reversal problems that transitions already solve.
Performance and Rendering
Transform and opacity are common choices because browsers can often update them without recalculating document layout. Animating width, height, margin, padding, top, or left may trigger layout. Large shadows, filters, and painted backgrounds can also be expensive. This does not prohibit them; it means the cost should match the value and be measured on representative devices.
/* Commonly efficient visual movement */
@keyframes move {
to { transform: translateX(100px); }
}
/* May perform layout work per frame */
@keyframes move-with-layout {
to { left: 100px; }
}
Animating hundreds of elements, even with transform, can produce CPU, GPU, memory, and battery costs. Infinite offscreen effects are especially wasteful. Pause or remove animation when it has no visible purpose. Use browser performance tools to inspect frame timing, layout, paint, and layer behavior.
will-change can hint that a property is likely to change, but it is not a universal performance switch. Applying it globally can allocate excessive compositing resources. Use it temporarily and only after profiling demonstrates a benefit.
Accessibility and Reduced Motion
Some users experience discomfort from large movement, spinning, zooming, parallax, or continuous animation. Respect prefers-reduced-motion by removing nonessential effects or replacing spatial movement with a brief, low-motion state change.
@media (prefers-reduced-motion: reduce) {
.spinner,
.floating-icon,
.animated-entrance {
animation: none;
}
}
Do not leave initial CSS hidden when the reduced-motion rule removes the animation responsible for revealing it. Stable content styles should be visible and usable without playback. If a loading spinner is removed, retain accessible loading text and status semantics.
Animation must not be the only indicator of error, success, focus, or selection. Keep focus rings persistent and easy to track. Avoid rapid flashing and high-frequency contrast changes, which can create serious safety problems. Provide controls to pause nonessential moving content when appropriate.
Motion Tokens and Reusable Keyframes
A design system should define a small motion vocabulary. Reuse intent-based keyframes and shared duration or easing tokens instead of creating a different fade for every component.
:root {
--duration-fast: 140ms;
--duration-standard: 240ms;
--ease-enter: cubic-bezier(0, 0, 0.2, 1);
}
@keyframes enter-fade-up {
from {
opacity: 0;
transform: translateY(0.5rem);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.popover {
animation:
enter-fade-up var(--duration-standard) var(--ease-enter) both;
}
Names should describe purpose rather than a specific component when reuse is genuine. Do not force unrelated components into one effect merely to reduce file size. Document where continuous animation is permitted and how each pattern behaves under reduced motion.
Debugging an Animation That Does Not Run
Start with computed styles. Confirm that the keyframes rule exists, the selected name matches, and duration is greater than zero. Check whether another rule or shorthand overrides the animation, whether play state is paused, whether iteration count is zero, and whether a reduced-motion rule disables playback.
Next inspect the animated declarations. Confirm the property can interpolate as intended and that start and end values are compatible. Check whether the element is rendered and whether the effect already completed before it became visible. A positive delay, forwards fill, or base style matching the keyframe can make an animation appear absent.
Browser animation panels can slow, replay, and visualize keyframe segments. Performance tools reveal dropped frames and expensive rendering work. Test class toggling, repeated triggers, interruption, element removal, tab visibility changes, and viewport resizing rather than validating only one ideal playback.
Animation Properties Reference
| Property | Purpose | Initial Value |
|---|---|---|
animation-name | Selects keyframes | none |
animation-duration | Defines one cycle length | 0s |
animation-timing-function | Maps time to segment progress | ease |
animation-delay | Offsets active playback | 0s |
animation-iteration-count | Sets cycle count | 1 |
animation-direction | Controls traversal direction | normal |
animation-fill-mode | Controls effect outside active time | none |
animation-play-state | Runs or pauses playback | running |
animation | Combines animation controls | Individual initial values |
Common Mistakes and Better Approaches
| Mistake | Better Approach |
|---|---|
| Name is set without a duration | Provide a duration greater than zero |
| Keyframe and animation names differ | Use clear, consistent identifiers |
| Animation handles a simple hover | Use a reversible transition |
| Every component moves continuously | Reserve infinite motion for real purpose |
| Forwards fill represents permanent state | Express stable state with normal selectors |
display is expected to interpolate visually | Animate compatible properties and manage state separately |
| Modal enters from hundreds of pixels away | Use restrained motion suited to ordinary UI |
| Many layout properties animate together | Prefer focused effects and profile rendering |
| Several animations overwrite transform | Combine transforms or separate wrapper layers |
| Reduced motion removes the content reveal | Keep stable content visible without animation |
| Animation is the only error or focus cue | Provide persistent semantic and visual state |
Cleanup assumes animationend always fires | Handle interruption and provide a fallback |
Common Interview Questions
What does @keyframes do? It defines named property values at offsets across one animation iteration.
What do from and to represent? They are aliases for the 0-percent and 100-percent keyframe selectors.
Why might animation-name appear not to work? The initial duration is zero, so a visible duration must normally be supplied.
How is an animation repeated forever? Set animation-iteration-count: infinite.
What does alternate direction do? It runs odd iterations forward and even iterations backward.
What does animation-fill-mode: forwards do? It retains the relevant ending keyframe effect after the animation completes.
What is a negative animation delay? It begins playback as though part of the animation timeline has already elapsed.
How do transitions differ from keyframes? Transitions interpolate when values change between states; keyframes can run autonomous, repeating, and multi-stage sequences.
Which properties are commonly preferred for UI animation? Transform and opacity are frequent choices, though actual performance must be measured.
How should reduced motion be handled? Remove or simplify nonessential movement while preserving content, feedback, and operability.
Practical Review Checklist
- Use keyframes only when repetition, autonomy, or multiple stages add value.
- Use clear, intent-based animation names.
- Set a visible duration and verify shorthand resets.
- Keep stable application state in normal CSS selectors.
- Use intermediate offsets only for meaningful stages.
- Choose timing per keyframe segment when the motion changes direction.
- Test positive and negative delays with fill modes.
- Verify normal, reverse, alternate, and fractional iteration behavior where used.
- Keep entrance distance and interaction duration restrained.
- Avoid unnecessary infinite motion and stop hidden effects.
- Prefer transform and opacity when they accurately express the effect.
- Profile many simultaneous animations on representative devices.
- Do not apply
will-changeglobally. - Provide reduced-motion behavior that leaves content visible and usable.
- Keep focus, error, loading, and success states understandable without motion.
- Test restart, pause, interruption, cancellation, and element removal.
- Filter animation events by name and provide lifecycle fallbacks.
- Use developer tools to inspect timelines, computed styles, and frame cost.
Key Takeaway
CSS keyframe animations separate a named sequence from its playback controls. Define meaningful offsets with @keyframes, then use animation properties to choose duration, easing, delay, iteration count, direction, fill behavior, and play state. Use transitions for straightforward changes between UI states and keyframes when an effect genuinely needs intermediate stages, repetition, or autonomous playback.
Production animation should be purposeful and resilient. Keep stable state outside the animation, favor restrained transform and opacity effects where appropriate, avoid uncontrolled infinite motion, and test interruptions and lifecycle events. Respect reduced-motion preferences and preserve all meaning and functionality without playback. The goal is not to make every element move; it is to use time and continuity to make important changes easier to understand.