GODRICH
#css-animation#button#loader#transition#scroll#dashboard#ui#design-trends

9 CSS Keyframe Animations, One Line, Copy-Paste

These nine CSS keyframe animation classes each wrap one keyframes block in a single class name, so adding that one class to any element runs the whole motion,

Auto-plays · click a tile to jump to its section · all nine in one zip

The nine below aren't ordered by name. They follow what a moving element is telling the page: 01 and 02 say it exists, 03 and 04 say it's working, 05 and 06 ask for a second look, and 07 through 09 mark a change that already happened. Skim the table below for the shape of each motion, then open any item's own section for the full class and its keyframes. Motion that stays in the frame rather than moving the whole box lives in 9 CSS border animations, and the focus-state versions built for form fields are collected in 9 CSS input focus animations.

Class What moves Duration Loop
.a-float translateY 3s infinite
.a-bounce translateY 1s infinite
.a-spin rotate 1s infinite
.a-pulse scale, opacity 2s infinite
.a-shake translateX 500ms once, on trigger
.a-wobble translateX, rotate 1s infinite
.a-flash opacity 1s infinite
.a-heartbeat scale 1.2s infinite
.a-jelly scaleX, scaleY 600ms once, on trigger

01Float up and down

A 16px rise on an ease-in-out curve

.a-float moves an element up 16px and back on an ease-in-out curve, so nothing about the motion reads as mechanical — it eases in and out the way a slow breath does.

Float up and down
Open in a new tab

01 · Float up and down — Measured area 4.311% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

translateYease-in-out3초

Holding a corner quietly

It suits an illustration or icon badge that needs to hold a corner of the screen without shouting for attention.

$duration: 3s;
$easing: ease-in-out;

.a-float { animation: float $duration $easing infinite; }

@keyframes float {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-16px); }
}

02Bounce

Why a css keyframe animation bounce needs five stops

.a-bounce runs the same translateY property as float but through five uneven keyframe stops instead of two, so it dips, overshoots, and settles the way something actually landing on a surface would.

Bounce
Open in a new tab

02 · Bounce — Measured area 0.997% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

translateY바운스 이징1초

Saying tap here without a label

Scroll-hint arrows and "load more" icons use it to say a tap here does something, with no label needed.

$duration: 1s;
$easing: cubic-bezier(.28, .84, .42, 1);

.a-bounce { animation: bounce $duration $easing infinite; }

@keyframes bounce {
  0%, 100% { transform: translateY(0); }
  30%      { transform: translateY(-4px); }
  55%      { transform: translateY(10px); }
  75%      { transform: translateY(-2px); }
  90%      { transform: translateY(4px); }
}

03Spin

Why linear, not an easing curve

.a-spin turns an element a full 360 degrees on a strict linear timing function, since any easing curve would make the rotation visibly speed up or slow down once per loop.

Spin
Open in a new tab

03 · Spin — Measured area 0.233% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

rotatelinear1초

Refresh and sync icons too

It works equally well on a refresh icon or a sync icon, not only on a dedicated loading spinner.

$duration: 1s;
$easing: linear;

.a-spin { animation: spin $duration $easing infinite; }

@keyframes spin {
  to { transform: rotate(360deg); }
}

04Pulse

Scale and opacity at the same midpoint

.a-pulse eases scale between 1 and 1.06 while opacity dips slightly at the same midpoint, so a live-status dot or a CTA button gets a soft nudge of attention without ever repainting the page's layout.

Pulse
Open in a new tab

04 · Pulse — Measured area 7.199% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

scaleopacity2초

A nudge that never repaints layout

The two properties move together, so the opacity dip and the scale change never look out of sync.

$duration: 2s;
$easing: ease-out;

.a-pulse { animation: pulse $duration $easing infinite; }

@keyframes pulse {
  0%, 100% { transform: scale(1);    opacity: 1; }
  50%      { transform: scale(1.06); opacity: .92; }
}

05Shake

Six steps that stop dead

.a-shake fires once on a trigger class rather than looping — six short left-right steps that finish in half a second and stop dead.

Shake
Open in a new tab

05 · Shake — Measured area 0.845% · moving frames 6/23Original by GODRICH · SCSS and React ship in the zip · See the password

translateX6회 왕복0.5초

Notice this, not error

It's built as a general "notice this" cue rather than a form-validation error, so it suits a new-feature badge just as well as a broken input.

$duration: 500ms;
$easing: cubic-bezier(.36, .07, .19, .97);
$amount: 8px;

.a-shake.is-shaking { animation: shake $duration $easing 1; }

@keyframes shake {
  10%, 90% { transform: translateX($amount * -.5); }
  20%, 80% { transform: translateX($amount); }
  30%, 70% { transform: translateX($amount * -1); }
  40%, 60% { transform: translateX($amount * .7); }
  50%      { transform: translateX($amount * -.7); }
}

06Wobble

Staggered percentages, uneven motion

.a-wobble layers translateX and rotate inside one keyframes block at staggered percentages, so it never settles into the clean back-and-forth of 05's shake.

Wobble
Open in a new tab

06 · Wobble — Measured area 2.026% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

translateX+rotate다단 keyframes1초

Move me, not something is wrong

A draggable card or a list item mid-reorder leans on that extra unevenness to read as "move me" rather than "something's wrong."

$duration: 1s;
$easing: ease-in-out;

.a-wobble { animation: wobble $duration $easing infinite; }

@keyframes wobble {
  0%, 100% { transform: translateX(0)    rotate(0deg); }
  15%      { transform: translateX(-6px) rotate(-4deg); }
  30%      { transform: translateX(5px)  rotate(3deg); }
  45%      { transform: translateX(-4px) rotate(-2deg); }
  60%      { transform: translateX(3px)  rotate(2deg); }
  75%      { transform: translateX(-2px) rotate(-1deg); }
}

07Flash

Stepping opacity, never to zero

.a-flash steps opacity from 1 down to roughly 0.15 and back, twice, instead of fading all the way to zero, so the element never fully disappears between blinks.

Flash
Open in a new tab

07 · Flash — Measured area 0.15% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

opacity2단 점멸1초

Readable on a light or a dark stage

An urgent-notice icon or a live-broadcast dot reads clearly against a light or a dark stage either way.

$duration: 1s;
$easing: ease-in-out;

.a-flash { animation: flash $duration $easing infinite; }

@keyframes flash {
  0%, 50%, 100% { opacity: 1; }
  25%, 75%      { opacity: .15; }
}

08Heartbeat

Two bumps and a rest

.a-heartbeat splits its keyframes into two quick scale bumps followed by a longer rest, copying the uneven beat-beat-pause rhythm of an actual pulse instead of a smooth, evenly spaced loop.

Heartbeat
Open in a new tab

08 · Heartbeat — Measured area 0.356% · moving frames 19/23Original by GODRICH · SCSS and React ship in the zip · See the password

scale 2단불균등 리듬1.2초

Why irregularity reads as alive

A like icon's idle state or a monitoring dashboard's status dot both benefit from that irregularity reading as "alive."

$duration: 1.2s;
$easing: ease-in-out;

.a-heartbeat { animation: heartbeat $duration $easing infinite; }

@keyframes heartbeat {
  0%, 100% { transform: scale(1); }
  14%      { transform: scale(1.24); }
  28%      { transform: scale(1); }
  42%      { transform: scale(1.18); }
  70%      { transform: scale(1); }
}

09Jelly wobble

scaleX and scaleY out of phase

.a-jelly moves scaleX and scaleY out of phase with each other, so an element squashes wide while it's short and tall while it's narrow before settling back into shape.

Jelly wobble
Open in a new tab

09 · Jelly wobble — Measured area 7.007% · moving frames 10/23Original by GODRICH · SCSS and React ship in the zip · See the password

scale(1.25, .75) 어긋남젤리 이징0.6초

Firing once, on release

It fires once, on release, which is exactly the timing an "added to cart" icon or a pressed button needs.

$duration: 600ms;
$easing: cubic-bezier(.68, -0.55, .27, 1.55);

.a-jelly.is-jellying { animation: jelly $duration $easing 1; }

@keyframes jelly {
  0%   { transform: scale(1, 1); }
  20%  { transform: scale(1.25, .75); }
  40%  { transform: scale(.75, 1.25); }
  60%  { transform: scale(1.15, .85); }
  80%  { transform: scale(.95, 1.05); }
  100% { transform: scale(1, 1); }
}

What order do the animation shorthand values go in?

The eight slots in order

Writing .a-float's animation as animation: float 3s ease-in-out infinite; only works because each value lands in a fixed slot — swap two of them and a duration can get read as a delay instead. MDN's animation shorthand reference lists the eight slots in this order:

Position Sub-property This post's example Default if omitted
1 animation-name float none
2 animation-duration 3s 0s
3 animation-timing-function ease-in-out ease
4 animation-delay (not set) 0s
5 animation-iteration-count infinite 1
6 animation-direction (not set) normal
7 animation-fill-mode (not set) none
8 animation-play-state (not set) running

What fill-mode decides

animation-fill-mode decides whether an element keeps a keyframe's styling before the animation starts or after it ends, and MDN's animation-fill-mode page documents all four keywords:

Value Holds the first frame before start Holds the last frame after end
none no no
forwards no yes
backwards yes no
both yes yes

Why 05 and 09 need no fill-mode

05's shake and 09's jelly both run with a bare 1 for iteration-count and no fill-mode at all, and neither needs one: their 0% and 100% keyframes both land back on the resting transform, so the element returns to its starting look the instant playback finishes. Making a css keyframe animation stop at end on a frame that doesn't match the resting style needs forwards added, or the element snaps back to its pre-animation state the moment the animation ends.

Where it breaks — the trap

Two classes, one animation shorthand

Stack two of these classes on one element — .a-float next to .a-spin, say — and only one of them actually moves. Each class declares the full animation shorthand, and the cascade lets whichever rule wins on order or specificity replace the other's value outright instead of merging with it. Combining two keyframes on the same element means writing animation-name: float, spin; and repeating animation-duration and animation-timing-function as matching comma-separated lists, one entry per name, in the same order. Even that isn't automatically safe: because float and spin both animate transform, the default animation-composition: replace still lets the second value in the list overwrite the first frame by frame, so only the rotation would show. Setting animation-composition: add — documented on MDN's animation-composition page — tells the browser to sum both transforms instead of replacing one with the other, so the element both floats and spins at once.

The smallest footprint of the nine

Item 07's flash measured the smallest footprint of the nine when this site's render check ran them: a 0.15% cumulative changed area, barely above the 0.08% floor the check requires before calling something "moving," since two opacity steps shift far fewer pixels than any transform in this set. The nine files already carry values tuned past that floor, unlocked with the password nft4q6u2, typed exactly as it reads on this line.

Item 07's thin margin above that floor is possible because its path is perfectly even; the opposite goal, a line that wobbles on purpose, needs a different set of keyframes entirely. The crayon hand-drawn landing sections build nine of those out of the same two properties used here.

Accessibility

What replaces the motion

All nine drop out under prefers-reduced-motion: reduce, replacing animation: name duration easing infinite with animation: none per class rather than letting a css keyframe animation repeat at a faster or shorter length. 05's shake keeps its meaning without motion by switching to a background-color change on .is-shaking instead of the translateX steps, so a motion-sensitive viewer still gets told something happened. The other eight simply hold whatever static frame they were already resting on — a flat badge, an unblinking dot, and a button at scale(1) — since none of them carry information a static frame would lose. Which operating-system settings flip this media query on is listed on MDN's prefers-reduced-motion page.

Where to go next

Browse more of this line of demos on the CSS category hub, and see what's actually free to read here versus what's only in the zip on the about page.

FAQ

Can I use a CSS keyframe animation without Sass or a build step?

Yes — each class compiles down to plain CSS in the zip, so linking that stylesheet and adding the matching class name is all .a-float or .a-bounce need to run. The $duration and $easing variables only exist in the SCSS source for retuning the timing before compiling; the shipped CSS file already has fixed values baked in. The same no-build rule holds for the SVG icon animation set, where a stroke rather than the whole box is what moves.

How do I run two css keyframe animation examples on one element at once?

List every name in animation-name separated by commas, then repeat animation-duration and animation-timing-function with one comma-separated value per name in the same order. If both animations touch the same property, such as transform, add animation-composition: add so the values combine instead of the later one silently overwriting the earlier one — the trap section above walks through a .a-float plus .a-spin example.

What does the React version in the zip do differently from the class toggle?

The react/ folder's components take duration, easing, and color as props and pass them through as CSS custom properties instead of hard-coded SCSS variables. For the two that only play once, 05's shake and 09's jelly, the component flips a shaking or jellying flag off and straight back on with useState, then clears it again in onAnimationEnd, so clicking the button a second time replays the animation instead of doing nothing.

Enter the archive password

The password is inside this article. You will find it as you read.