Intro to CSS Animation: @keyframes, the animation Property, and vs. transition
CSS animation lets you move elements smoothly without writing any JavaScript. This article explains what @keyframes is, the meaning of the animation properties duration, timing-function, delay, iteration-count, and direction, the difference from transition, how to build classic effects like fade, slide, and spin, and the performance tips that keep motion smooth.
On a website, a button color softly shifts when you hover over it, a menu slides smoothly into view, a loading icon spins around and around... much of this "motion" is built with CSS animation alone. Without writing a single line of JavaScript, you can make elements change over time using only CSS. This article walks beginners step by step through the basics of CSS animation: the @keyframes rule and the animation property.
What Is CSS Animation
CSS animation is a mechanism for changing how an element looks over time using only CSS. You can smoothly transition properties such as color, position, size, opacity, and rotation from a "start state" to an "end state," or even through finely specified intermediate steps.
CSS animation is made of two parts. One is the blueprint of the motion (@keyframes), which describes which properties change and how. The other is the animation property, which specifies how many seconds the blueprint plays over, with what speed curve, and how many times. Thinking of the blueprint and the playback settings separately makes CSS animation far easier to understand.
Because you can express motion without JavaScript, the code is simple and the browser can optimize it well. It shines in a wide range of situations, from a small hover flourish to a loading spinner to a blink that draws attention to an element.
Defining Motion with @keyframes
The @keyframes rule is where you write the blueprint of the motion. You give the animation a name and specify styles at each point of progress (0% to 100%, or from and to).
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
This is an animation named "fade-in" designed so that at the start (from = 0%) it is transparent and at the end (to = 100%) it is opaque. The browser automatically interpolates the values in between, producing a soft fade-in.
You can specify not only 0% and 100% but also intermediate steps. For example, the following creates a bouncing up-and-down motion.
@keyframes bounce {
0% { transform: translateY(0); }
50% { transform: translateY(-20px); }
100% { transform: translateY(0); }
}
The more finely you slice the progress, the more complex the motion you can express. That said, building up which value to use at which percentage entirely in your head is hard. The CSS Keyframes Generator, which lets you preview motion as it writes out the @keyframes code, makes it easy to design the blueprint while checking it visually.
Inside the animation Property
Once you have the blueprint in @keyframes, you apply it to an element and specify how it plays. That job belongs to the animation properties. Get comfortable with these five common ones.
| Property | Role | Example |
|---|---|---|
| animation-duration | Time for one playback | 0.6s / 600ms |
| animation-timing-function | The speed curve | ease / linear / cubic-bezier(...) |
| animation-delay | Wait before starting | 0.2s |
| animation-iteration-count | Number of repeats | 1 / 3 / infinite |
| animation-direction | Playback direction | normal / reverse / alternate |
duration is how many seconds the whole animation takes. If you do not set it, it defaults to 0 seconds and no motion is visible, so always set it. timing-function determines the speed curve, such as starting slow and speeding up later. Besides keywords like ease and linear, you can build any curve with cubic-bezier(). To dial in that curve, the cubic-bezier Generator, which lets you adjust the curve visually, is a great help.
delay is the wait before playback starts, useful for "staggered" effects that move several elements at slightly different times. iteration-count is the number of plays; setting infinite creates an endless loop, used for loading spinners and the like. direction with alternate plays back and forth, producing a natural there-and-back motion.
You can also combine these into a single shorthand line, such as animation: fade-in 0.6s ease 0.2s 1 normal;. If the order confuses you, it is fine to start with the individual properties until you get used to it.
The Difference from transition
Besides animation, CSS offers transition for adding motion. They are similar, but their strengths are clearly different.
| transition | animation | |
|---|---|---|
| Trigger | Needs a state change (e.g. hover) | Starts automatically once applied |
| Intermediate steps | Start and end only | Can specify steps via @keyframes |
| Repeating | Not possible | Possible via iteration-count |
| Best for | Simple changes on hover | Complex, automatic, repeating motion |
transition interpolates motion only "when one state changes to another." For example, if you set a color on a button with :hover, the color shifts smoothly only at the moment you hover. It is ideal for simple cases that connect two points, a start and an end.
animation, on the other hand, starts playing automatically just by being applied, with no triggering state change needed. You can specify intermediate steps finely with @keyframes, and it supports endless loops and back-and-forth playback, so it suits complex, automatic motion such as loading indicators or continuous movement that draws attention. A handy rule of thumb: use transition for simple hover flourishes, and animation for complex, automatic, or repeating motion.
Common Effects: Fade, Slide, Spin
Here are the classic effects that come up again and again in practice, along with the thinking behind them. All can be built by combining @keyframes and animation.
1. Fade (soft appearance)
Simply changing opacity from 0 to 1 makes an element appear softly. Going from 1 to 0 is a fade-out. It is the simplest and most frequently used effect, often used when a page loads.
2. Slide (gliding in)
Starting from a position shifted with transform: translateX() or translateY() and returning to the original position (0) makes the element glide in from the side or from above or below. Combining it with a fade so it "rises softly from below while appearing" gives a refined impression.
3. Spin (rotating)
Changing transform: rotate(0deg) to rotate(360deg) and combining iteration-count: infinite with timing-function: linear creates a loading spinner that keeps rotating at a constant speed. linear is used because rotation looks more natural at a constant speed.
You can write the @keyframes for these by hand, but the CSS Keyframes Generator, which generates code while you preview, lets you iterate on adjustments and checks quickly. When you want to perfect the speed curve, also fine-tune the timing-function with the cubic-bezier Generator to make the motion noticeably more polished.
Performance Tips
For animation, looking smooth is everything. Janky motion gives a cheap impression instead. The single most important factor for staying smooth is which property you animate.
- Prefer transform and opacity: do movement, scaling, and rotation with
transform, and showing or hiding withopacity. Browsers can process these efficiently on the GPU, so they tend to move smoothly. - Avoid continuously changing width, height, top, and left: changing size or position with these every frame triggers frequent layout recalculation (reflow), a common cause of jank. To move position, use
transform: translate()rather thantop/left. - Do not animate too many elements at once: animating a large number of elements all at once overwhelms low-end devices. Limit the count, or stagger the timing.
Do not forget accessibility, either. For users sensitive to motion, it is considerate to provide @media (prefers-reduced-motion: reduce), which respects the OS "reduce motion" setting, and to keep animation restrained in that case.
CSS animation is easiest to handle when you think of it in two stages: build the blueprint of the motion with @keyframes, then specify how it plays with the animation property. Start by building and running a fade or slide in the CSS Keyframes Generator, and fine-tune the speed curve with the cubic-bezier Generator. Moving your hands and confirming with your eyes is the fastest path to improvement.
Please note that this article is intended to provide general information. CSS specifications and browser support can change, so when implementing, please also check the latest official documentation and each browser's support information before relying on it.
FAQ
Should I use CSS animation or transition?
For a simple change from one state to another, like a color shift on hover, transition is the easier choice. For complex, automatic, or repeating motion, where you want to specify intermediate steps, loop endlessly, play back and forth, or start automatically with no trigger, animation (@keyframes) is the better fit. Choose based on the use case.
Are from/to and 0%/100% different in @keyframes?
They mean essentially the same thing: from corresponds to 0% and to corresponds to 100%. For simple motion with only a start and end, from/to reads cleanly; when you also want intermediate steps (25%, 50%, etc.), percentage notation is handy. You can mix them, but keeping the notation consistent within one animation is clearer.
What should I set for the animation timing-function?
It determines the speed curve. ease is the go-to for natural motion, while linear is standard when constant speed is desired, such as rotation. For finer control, you can build a custom curve with cubic-bezier(). The cubic-bezier Generator, which lets you adjust the curve visually, makes it easier to find exactly the speed feel you want.
Why does my animation look janky?
Changing width, height, top, or left every frame triggers frequent layout recalculation, which tends to cause jank. Do movement, scaling, and rotation with transform, and showing or hiding with opacity, so the browser can process them efficiently and smoothly. Limiting how many elements move at once also helps.
Is the code I create in the generators sent to a server?
WAZA TOOLS' CSS Keyframes Generator and cubic-bezier Generator run entirely in your browser. What you enter and the code you generate are never sent to or stored on a server. They work on smartphones too, and you can use them as many times as you like, for free.