Free Online Code Editor
Learn when to use CSS transitions and when to use keyframe animations, which properties stay smooth, and how to respect users who prefer reduced motion.
Small bits of motion make an interface feel alive: a button that lifts when you hover, a menu that slides in, a spinner that shows something is loading. CSS can do all of it without JavaScript.
There are two tools, and picking the right one is mostly a question of whether the motion needs a trigger.
@keyframes) runs on its own and can have many steps, repeat, and loop.Hover effect? Transition. Spinner or attention pulse? Animation.
Add transition to the element's normal state, and tell it which property to animate and how long it should take.
<style>
.btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
background: #16a34a;
color: white;
font-size: 16px;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.btn:hover {
transform: translateY(-3px);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
}
.btn:active {
transform: translateY(0);
}
</style>
<button class="btn">Hover me</button>The browser fills in all the frames between the normal state and the hover state. When you move the mouse away, it animates back, because the transition rule is on the normal state.
A transition has four parts: the property (transform), the duration (0.2s), the timing function (ease), and an optional delay. Avoid transition: all. It animates properties you didn't intend and can cause surprise jank.
The timing function controls the acceleration.
ease starts and ends gently (the default).ease-out starts fast and slows down, which feels natural for things entering the screen.ease-in starts slowly and speeds up, which suits things leaving.linear has constant speed, which is right for spinners and progress bars.For UI feedback, 150 to 300 milliseconds feels responsive. Over half a second starts to feel slow.
When you need steps, loops, or motion that starts by itself, define the frames with @keyframes and apply them with animation.
<style>
.spinner {
width: 40px;
height: 40px;
border: 5px solid #bbf7d0;
border-top-color: #16a34a;
border-radius: 50%;
animation: spin 0.9s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
<div class="spinner"></div>When you leave out the from frame, the browser uses the element's current style as the starting point. This spinner only defines where it ends up.
Here's a multi-step example, a gentle pulse for drawing attention:
<style>
.badge {
display: inline-block;
padding: 6px 14px;
border-radius: 999px;
background: #dc2626;
color: white;
font-family: sans-serif;
animation: pulse 1.6s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.12); }
}
</style>
<span class="badge">New</span>animation-duration and animation-timing-function work like transitions.animation-iteration-count can be a number or infinite.animation-direction: alternate plays forward, then backward.animation-delay waits before starting. Use it to stagger items.animation-fill-mode: forwards keeps the final frame after the animation ends. Without it, the element snaps back to its original style.A fade-in on page load, with staggered items, shows several of these at once:
<style>
li {
opacity: 0;
animation: fade-up 0.5s ease-out forwards;
font-family: sans-serif;
}
li:nth-child(2) { animation-delay: 0.15s; }
li:nth-child(3) { animation-delay: 0.30s; }
@keyframes fade-up {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
<ul>
<li>First</li>
<li>Second</li>
<li>Third</li>
</ul>Browsers can animate transform and opacity very cheaply, often on the graphics card, so they stay smooth even on slow phones. Animating width, height, top, left, or margin forces the browser to recalculate the layout on every frame, which can stutter.
So prefer:
transform: translateX(...) instead of changing left.transform: scale(...) instead of changing width and height.opacity for fades.If you really need to animate a height, like an accordion, test it on a mid-range phone, not just your laptop.
Some people get dizzy or distracted from lots of movement. Operating systems have a "reduce motion" setting, and CSS can read it:
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}It's a few lines of CSS and makes your site comfortable for more people. Essential feedback still happens. It's just instant.
transition on the hover state only. The motion works going in but snaps going out. Put it on the base rule.forwards. The element jumps back to where it started when the animation ends.transform and opacity for smooth results.prefers-reduced-motion.Reading helps, but typing the code is what makes it stick. Open the editor, paste any example from this article, and change it until it breaks.
Open the HTML/CSS editorStop guessing between Flexbox and CSS Grid. Learn the one question that decides it, with copy-and-run layouts for navbars, card galleries, and page templates.
Validate forms with plain HTML attributes, style valid and invalid states with CSS, and use the Constraint Validation API for custom messages.
Build pages that work on phones, tablets, and desktops. Covers the viewport tag, clamp(), mobile-first media queries, flexible images, and testing.