SolutionsCase StudiesAI ToolsResourcesAboutContact
Get Your Free AI Automation Audit Book a Free 30-Minute Strategy Call
Playground~/frontend Open editor

~/css/keyframe-animations

7 CSS Keyframe Animations

4 Pure CSS3 with JSPublished 2026-10-05Free · no sign-up

Seven demos that explain how CSS @keyframes animations work: a dot following a square path, animation-fill-mode and animation-direction side by side, iteration counts, pausing with play-state, three animations on one element and a reusable keyframe rule that reads custom properties.

01. Moving Along a Square Path

@keyframesBasics

A dot travels round the four corners of a square using five keyframes at 0, 25, 50, 75 and 100 percent. It shows the core idea of @keyframes: you describe positions at points in time and the browser fills in everything between.

View code
<div class="kf1" aria-hidden="true"><span></span></div>
.kf1 { position: relative; width: 140px; height: 140px; border: 2px dashed #d0d5dd; border-radius: 12px; }
.kf1 span { position: absolute; left: -11px; top: -11px; width: 20px; height: 20px; border-radius: 50%; background: #7f56d9; animation: kf1 4s ease-in-out infinite; }
@keyframes kf1 { 0%, 100% { transform: translate(0, 0); } 25% { transform: translate(140px, 0); } 50% { transform: translate(140px, 140px); } 75% { transform: translate(0, 140px); } }
@media (prefers-reduced-motion: reduce) { .kf1 span { animation: none; } }

02. animation-fill-mode Compared

@keyframesfill-mode

Four bars run the same two-second animation with a one-second delay and fill-mode none, forwards, backwards and both. Press Run and watch where each bar waits before it starts and where it stays after it ends, which is all fill-mode controls.

View code
<div class="kf2"><p><span>none</span><i style="--f:none"></i></p><p><span>forwards</span><i style="--f:forwards"></i></p><p><span>backwards</span><i style="--f:backwards"></i></p><p><span>both</span><i style="--f:both"></i></p><button>Run</button></div>
.kf2 { display: grid; gap: 8px; width: 290px; max-width: 100%; font: 600 12px "JetBrains Mono", ui-monospace, monospace; color: #94a3b8; }
.kf2 p { display: grid; grid-template-columns: 80px 1fr; align-items: center; margin: 0; }
.kf2 i { display: block; width: 24px; height: 18px; border-radius: 4px; background: #64748b; }
.kf2.run i { animation: kf2 2s ease 1s; animation-fill-mode: var(--f); }
@keyframes kf2 { from { transform: translateX(60px); background: #f59e0b; } to { transform: translateX(160px); background: #22c55e; } }
.kf2 button { justify-self: start; margin-top: 6px; padding: 8px 14px; border: 0; border-radius: 8px; background: #e2e8f0; color: #0f172a; font: 700 13px Inter, system-ui, sans-serif; cursor: pointer; }
.kf2 button:focus-visible { outline: 2px solid #93c5fd; outline-offset: 2px; }
@media (max-width: 300px) { .kf2 p { grid-template-columns: 1fr; } }
var w = document.querySelector('.kf2');
w.querySelector('button').onclick = function () { w.classList.remove('run'); void w.offsetWidth; w.classList.add('run'); };

03. animation-direction Compared

@keyframesdirection

The same left-to-right keyframes played normal, reverse, alternate and alternate-reverse. alternate is the one that makes a loop go back and forth smoothly instead of jumping back to the start, which is why it suits breathing and floating effects.

View code
<div class="kf3"><p><span>normal</span><i style="--d:normal"></i></p><p><span>reverse</span><i style="--d:reverse"></i></p><p><span>alternate</span><i style="--d:alternate"></i></p><p><span>alt-reverse</span><i style="--d:alternate-reverse"></i></p></div>
.kf3 { display: grid; gap: 10px; width: 290px; max-width: 100%; font: 600 12px "JetBrains Mono", ui-monospace, monospace; color: #475467; }
.kf3 p { position: relative; display: flex; align-items: center; height: 22px; margin: 0; padding-left: 92px; }
.kf3 span { position: absolute; left: 0; }
.kf3 i { width: 20px; height: 20px; border-radius: 50%; background: #2563eb; animation: kf3 1.6s ease-in-out infinite; animation-direction: var(--d); }
@keyframes kf3 { from { transform: translateX(0); } to { transform: translateX(150px); } }
@media (max-width: 320px) { @keyframes kf3 { to { transform: translateX(120px); } } }
@media (prefers-reduced-motion: reduce) { .kf3 i { animation: none; } }

04. Iteration Count Compared

@keyframesiteration

Three stars spin once, three times and forever after you press Run. animation-iteration-count also accepts fractions, so 2.5 would stop halfway through a third spin; the infinite one keeps going until reduced motion or a pause stops it.

View code
<div class="kf4"><p><i style="--n:1" aria-hidden="true">★</i><span>1</span></p><p><i style="--n:3" aria-hidden="true">★</i><span>3</span></p><p><i style="--n:infinite" aria-hidden="true">★</i><span>infinite</span></p><button>Run</button></div>
.kf4 { display: flex; flex-wrap: wrap; align-items: end; justify-content: center; gap: 22px; font: 600 12px "JetBrains Mono", ui-monospace, monospace; color: #94a3b8; }
.kf4 p { display: grid; justify-items: center; gap: 4px; margin: 0; }
.kf4 i { display: block; color: #fbbf24; font-size: 34px; font-style: normal; }
.kf4.run i { animation: kf4 .8s linear var(--n); }
@keyframes kf4 { to { transform: rotate(1turn); } }
.kf4 button { flex-basis: 100%; max-width: 80px; padding: 8px; border: 0; border-radius: 8px; background: #e2e8f0; color: #0f172a; font: 700 13px Inter, system-ui, sans-serif; cursor: pointer; }
.kf4 button:focus-visible { outline: 2px solid #93c5fd; outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .kf4.run i { animation-iteration-count: 1; } }
var w = document.querySelector('.kf4');
w.querySelector('button').onclick = function () { w.classList.remove('run'); void w.offsetWidth; w.classList.add('run'); };

05. Pause and Play with play-state

@keyframesplay-state

A spinning disc that pauses exactly where it is and resumes from the same spot, because animation-play-state freezes the animation instead of resetting it. The button is a toggle with aria-pressed and a label that names the next action.

View code
<div class="kf5"><span class="disc" aria-hidden="true"></span><button aria-pressed="false">Pause</button></div>
.kf5 { display: grid; justify-items: center; gap: 12px; font-family: Inter, system-ui, sans-serif; }
.kf5 .disc { width: 110px; height: 110px; border-radius: 50%; background: radial-gradient(circle, #101828 0 8px, #f2f4f7 9px 14px, transparent 15px), repeating-radial-gradient(circle, #1f2937 0 2px, #111827 2px 4px); box-shadow: inset 0 0 0 30px transparent; position: relative; animation: kf5 3s linear infinite; }
.kf5 .disc::after { content: ""; position: absolute; left: 50%; top: 6px; width: 4px; height: 18px; border-radius: 2px; background: #f59e0b; }
.kf5.paused .disc { animation-play-state: paused; }
.kf5 button { padding: 8px 16px; border: 1px solid #d0d5dd; border-radius: 8px; background: #fff; font: 700 13px Inter, system-ui, sans-serif; cursor: pointer; }
.kf5 button:focus-visible { outline: 3px solid #84adff; outline-offset: 2px; }
@keyframes kf5 { to { transform: rotate(1turn); } }
@media (prefers-reduced-motion: reduce) { .kf5 .disc { animation: none; } }
var w = document.querySelector('.kf5'), b = w.querySelector('button');
b.onclick = function () { var p = w.classList.toggle('paused'); b.setAttribute('aria-pressed', p); b.textContent = p ? 'Play' : 'Pause'; };

06. Two Animations on One Element

@keyframesMultiple

An element can run several animations at once by listing them, comma separated, each with its own duration and timing. Here a float, a slow colour change and a gentle tilt combine, and because each animates a different property they never fight.

View code
<div class="kf6" aria-hidden="true"></div>
@property --kf6c { syntax: "<color>"; initial-value: #38bdf8; inherits: false; }
.kf6 { width: 90px; height: 90px; border-radius: 26px; background: var(--kf6c); box-shadow: 0 20px 30px -18px var(--kf6c); animation: kf6a 2.4s ease-in-out infinite alternate, kf6b 6s linear infinite alternate, kf6c 3.1s ease-in-out infinite alternate; }
@keyframes kf6a { to { translate: 0 -18px; } }
@keyframes kf6b { to { --kf6c: #f472b6; } }
@keyframes kf6c { from { rotate: -6deg; } to { rotate: 6deg; } }
@media (prefers-reduced-motion: reduce) { .kf6 { animation: none; } }

07. Reusable Keyframes with Variables

@keyframesCustom properties

One @keyframes rule that reads custom properties, so each element can pass its own distance and angle. Three badges slide in from different directions using the same animation, which keeps a design system's motion small and consistent.

View code
<div class="kf7"><span style="--x:-40px;--y:0;--r:-8deg">New</span><span style="--x:0;--y:-30px;--r:0deg">Sale</span><span style="--x:40px;--y:0;--r:8deg">Hot</span></div>
.kf7 { display: flex; gap: 12px; font: 800 15px Inter, system-ui, sans-serif; }
.kf7 span { padding: 8px 14px; border-radius: 999px; background: #101828; color: #fff; animation: kf7 .7s cubic-bezier(.2,.8,.2,1) both; }
.kf7 span:nth-child(2) { background: #b42318; animation-delay: .1s; } .kf7 span:nth-child(3) { background: #b54708; animation-delay: .2s; }
@keyframes kf7 { from { opacity: 0; transform: translate(var(--x), var(--y)) rotate(var(--r)); } }
@media (prefers-reduced-motion: reduce) { .kf7 span { animation: none; } }

About these CSS Keyframe Animations

Keyframes describe moments

A @keyframes rule lists states at percentages of the animation's duration. The browser interpolates everything between them, so a square path needs only five keyframes for its four corners.

fill-mode decides before and after

With a delay, backwards applies the first keyframe while waiting; forwards keeps the last keyframe after the end; both does both. Most entrance animations want both.

Combine without conflict

An element can run several animations at once, comma separated. If each animates a different property, such as translate, rotate and a colour, they combine cleanly.

Reuse with custom properties

Keyframes can read custom properties from the element they run on. One rule with var(--x) serves many elements that each set their own value.

Frequently asked questions

What is the difference between keyframes and transitions?

Transitions animate between two states when something changes; keyframes run their own sequence, can loop and can have many steps.

Can I restart a keyframe animation?

Remove it, force a reflow by reading offsetWidth and add it again, as the Run buttons here do.

Why use the individual translate and rotate properties?

They animate independently, so two animations can move and turn the same element without overwriting each other's transform.