Get affordable and hassle-free WordPress hosting plans with Cloudways — start your free trial today.
The @keyframes
at-rule sets the value of properties at different points during an animation, so instead of defining how each property should behave at each frame of an animation, we set its keyframes, and CSS will figure out (i.e., interpolate) the values between them.
You can think of the animation
property with @keyframes
as the big brother to the transition
property, allowing control between steps of the animation sequence.
/* Define the animation */
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(720deg);
}
}
/* Apply the animation */
img {
animation: spin 1s infinite;
}
The @keyframes
at-rule is defined in the CSS Animations Level 1 specification.
Syntax
@keyframes = @keyframes <keyframes-name> { <qualified-rule-list> }
<keyframes-name> = <custom-ident> | <string>
<keyframe-block> = <keyframe-selector># { <declaration-list> }
<keyframe-selector> = from | to | <percentage [0,100]>
Cool, but a more readable syntax would be:
@keyframes <keyframes-name> {
<keyframe-selector> {
<declaration-list>
}
<keyframe-selector> {
<declaration-list>
}
}
Arguments
/* `from` and `to` selectors */
@keyframes my-animation {
from {
transform: translate(-100px);
}
to {
transform: translate(100px);
}
}
/* Percentages form */
@keyframes my-animation {
0% {
transform: translate(100px);
}
50% {
transform: translate(-100px);
}
100% {
transform: translate(100px);
}
}
/* We can combine percentages */
@keyframes my-animation {
0%,
100% {
transform: translate(100px);
}
50% {
transform: translate(-100px);
}
}
<keyframes-name>
: Either a<custom-ident>
or<string>
representing the name of the@keyframes
. If a<custom-ident>
is used, then it cannot match any CSS keyword likenone
orinitial
.<keyframe-selector>
: Specifies a point in the animation. It can be a percentage from0%
to100%
, so33%
is a third of the animation,50%
a half, and so on. You can also use thefrom
andto
selectors which equal0%
and100%
, respectively. Values outside the0%
to100%
range are ignored.<declaration-list>
: Written as normal CSS declarations, it sets the properties on each keyframe.
Basic usage
The simplest @keyframes
at-rule only has a final keyframe, defined with the to
or 100%
selector. For example, the following @keyframes
will animate from the element’s current width
to 50%
.
@keyframes resize {
to {
width: 50%;
}
}
Then we can use it inside the animation
property along the time of the animation:
.element {
animation: resize 1.5s infinite alternate; /* infinite and alternate for demo */
}
We can also set the initial keyframe, defined with the from
or 0%
selector:
@keyframes resize {
from {
width: 50%;
}
to {
width: 25%;
}
}
We can also add keyframes in between, so we could set a keyframe at half of the animation with 50%
:
@keyframes resize {
from {
width: 50%;
}
50% {
width: 75%;
}
to {
width: 25%;
}
}
And don’t forget! We can animate almost all properties inside a @keyframes
:
@keyframes remake {
from {
width: 25%;
border-radius: 60% 40% 70% 30% / 30% 30% 70% 70%;
background-color: lch(77% 91 135);
}
to {
width: 50%;
border-radius: 60% 40% 33% 67% / 30% 45% 55% 70%;
background: lch(77% 93 165);
}
}
@keyframes
together
Using several The last snippet was one @keyframe
packing a lot of different properties, but it may be better to animate fewer properties in different @keyframes
and then combine their effects on one animation
property.
@keyframes resize {
/* ... */
}
@keyframes reborder {
/* ... */
}
@keyframes recolor {
/* ... */
}
That way, we can create different animations depending on the element.
#first {
animation: resize 1.5s infinite alternate, recolor 1.5s infinite alternate;
}
#second {
animation: resize 1.5s infinite alternate, reborder 1.5s infinite alternate;
}
Common custom properties won’t work
As a recap, @keyframes
defines the element’s styles at specific points throughout the animation, and the browser will figure out the values between them. If we give it an initial and final width
, it will gradually increase/decrease the pixels used, or in the case of color, the browser will move through the corresponding color space to reach the final color. This process of the browser making out the in-betweens is called interpolation and works quite well in @keyframes
with one exception: custom properties.
For example, if we try to animate a color saved in a custom property:
@keyframes repaint {
from,
to {
--my-color: lch(77% 91 135);
}
50% {
--my-color: lch(77% 93 165);
}
}
.blob {
background-color: var(--my-color);
animation: repaint 2s infinite;
}
Notice the flex! We are using lch()
instead of rgb()
or hsl()
!
It will abruptly change colors and forget to animate them. Well, it doesn’t actually forget, CSS simply can’t animate it. If we declare an inline custom property, CSS treats its content as a string rather than a number and we can’t interpolate that! Instead, we have to let CSS know that the custom property holds a color using the @property
at-rule:
@property --my-color {
syntax: "<color>";
initial-value: lch(77% 91 135);
inherits: true;
}
/* ... */
And now CSS can interpolate the values in between the color and give us an actual animation:
You can read more about this in “Interpolating Numeric CSS Variables”. And while we’re talking about animating colors, you might find it interesting that CSS uses color mixing when interpolating between color values.
@keyframes
Changing the animation in All animation
functions are usually ignored inside @keyframes
; it would be counter intuitive to change the animation behavior while it’s running and could also break it by changing its animation-name
or animation-duration
. However, there is one exception: the animation-timing-function
.
The animation-timing-function
property has a special interpretation inside @keyframes
since it defines the timing function used for the next keyframes.
@keyframes resize {
from {
width: 100%;
animation-timing-function: ease-out;
}
50% {
width: 50%;
animation-timing-function: ease-in;
}
to {
width: 100%;
}
}
.element {
animation: resize 2.5s infinite;
}
Note: Using animation-timing-function
on the 100%
mark will be ignored since the animation will be finished at that point.
@keyframes
Some details about The @keyframes
at-rule has a lot of details that, while not extremely important, are likely to be overlooked while using it. I did my best to gather some below and hopefully save someone a feature headache:
@keyframes
Names in Writing the name of @keyframe
as a string or a custom ident doesn’t make any difference, so the following @keyframe
rules are exactly the same, with the latter replacing the former:
@keyframes my-animation { /* ... */ }
@keyframes "my-animation" { /* ... */ }
Although uppercase and lowercase characters do make a difference since @keyframes
is case-sensitive and matched codepoint-by-codepoint. For example, the following @keyframes
rule is different from the last two:
@keyframes My-Animation { /* ... */ }
@keyframes
is element agnostic
You’ll notice there isn’t any mention of elements when writing @keyframes
. This is a design choice, since @keyframes
rules are written to be reused on different elements. Then, it’s worth noticing that values and units relative to an element (like em
, %
, currentColor
, etc.) don’t have an intrinsic value in @keyframes
, but once used in an element’s animation
property will reference that element.
@keyframes
Discrete properties in Each property in CSS has an “animation type”, which sets how it behaves when we try to animate or transition it. There are three animation types: animatable
, discrete
, and not animatable
. animatable
properties are interpolable, i.e. if given a start and end value, CSS can make the values in between and animate, like width
or color
. However, discrete
properties (like text-align
or flex-direction
) don’t have values in between, so if we try to animate them using @keyframes
CSS will abruptly switch them at the middle of the animation.
There is one exception, the display
and content-visibility
properties which are both discrete, but if used inside @keyframes
they will be ignored. This exception also has another exception, recently Chromium 116 fixed it and we can now use it, but in the rest of the browsers, both properties will still be ignored.
Duplicated selectors are joined
Lastly, if we write the same <keyframe-selector>
more than once, all declaration lists will be valid and the latter won’t replace the previous selector, unless they have different properties, in which case only the property gets replaced. For example, both <keyframe-selector>
will get executed in the following @keyframes
:
@keyframes remake {
/* ... */
50% {
background-color: blue;
}
50% {
width: 400px;
}
/* ... */
}
It would be the same as writing:
@keyframes remake {
/* ... */
50% {
background-color: blue;
width: 400px;
}
/* ... */
}
However, if both declarations have the same property, the latter wins. So in the following @keyframes
, only the last declaration gets executed:
@keyframes remake {
/* ... */
50% {
background-color: blue;
}
50% {
background-color: red;
}
/* ... */
}
And it would be the same as writing:
@keyframes remake {
/* ... */
50% {
background-color: red;
}
/* ... */
}
Demo
Specification
The @keyframes
at-rule is defined in the CSS Animations Level 1 specification.
Browser support
The @keyframe
at-rule is widely supported by all modern browsers and legacy browsers including Internet Explorer 10+.
More information
- Four new CSS features for smooth entry and exit animations (Una Kravets and Joey Arhar)
- An Introduction To CSS Keyframes Animation (Smashing Magazine)