16 CSS Bounce Animations13 / 16
Bouncing Letters Wave
Individual letters in a word bounce up in sequence, creating a wave motion — the classic 404-page / playful branding pattern. Deep pink surface with the letters of "BOUNCE" spelled out, each letter animated with staggered animation-delay. Every letter runs the SAME translateY bounce keyframe but starts at a different point in the cycle, creating the wave illusion. Users search for this when building error pages, empty states, or playful hero moments.
Published
The code
<div class="bn-13">
<p class="bn-13__label">Bouncing letters wave</p>
<p class="bn-13__word" aria-label="Bounce">
<span aria-hidden="true">B</span>
<span aria-hidden="true">O</span>
<span aria-hidden="true">U</span>
<span aria-hidden="true">N</span>
<span aria-hidden="true">C</span>
<span aria-hidden="true">E</span>
</p>
<p class="bn-13__hint">Each letter uses the same bounce keyframe with staggered <code>animation-delay</code>.</p>
</div><div class="bn-13">
<p class="bn-13__label">Bouncing letters wave</p>
<p class="bn-13__word" aria-label="Bounce">
<span aria-hidden="true">B</span>
<span aria-hidden="true">O</span>
<span aria-hidden="true">U</span>
<span aria-hidden="true">N</span>
<span aria-hidden="true">C</span>
<span aria-hidden="true">E</span>
</p>
<p class="bn-13__hint">Each letter uses the same bounce keyframe with staggered <code>animation-delay</code>.</p>
</div>.bn-13 {
--page-bg: #831843;
--ink: #fdf2f8;
--sub: rgba(253,242,248,.7);
--accent: #f9a8d4;
--accent-hi: #f0abfc;
font-family: -apple-system,BlinkMacSystemFont,'Poppins','Inter','Segoe UI',sans-serif;
width: 100%;
min-height: 100vh;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 32px;
padding: 32px;
background: radial-gradient(60% 60% at 50% 40%, rgba(240,171,252,.16) 0%, transparent 60%), linear-gradient(135deg,#831843 0%,#9d174d 50%,#701a75 100%);
color: var(--ink);
text-align: center;
}
.bn-13 *,
.bn-13 *::before,
.bn-13 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.bn-13 ::selection {
background: var(--accent);
color: #831843;
}
.bn-13__label {
font-size: .9rem;
font-weight: 600;
color: var(--sub);
letter-spacing: .14em;
text-transform: uppercase;
}
.bn-13__word {
display: inline-flex;
align-items: baseline;
gap: 6px;
font-family: 'Poppins','Inter',sans-serif;
font-size: clamp(4rem,10vw,7rem);
font-weight: 900;
letter-spacing: -.03em;
line-height: 1.2;
color: var(--accent-hi);
}
.bn-13__word span {
display: inline-block;
animation: bn-13-letter 1.8s cubic-bezier(.55,0,.45,1) infinite;
will-change: transform;
}
.bn-13__word span:nth-child(1) {
animation-delay: 0s;
}
.bn-13__word span:nth-child(2) {
animation-delay: .12s;
}
.bn-13__word span:nth-child(3) {
animation-delay: .24s;
}
.bn-13__word span:nth-child(4) {
animation-delay: .36s;
}
.bn-13__word span:nth-child(5) {
animation-delay: .48s;
}
.bn-13__word span:nth-child(6) {
animation-delay: .60s;
}
@keyframes bn-13-letter {
0% {
transform: translateY(0);
}
30% {
transform: translateY(-20px);
}
60% {
transform: translateY(-8px);
}
100% {
transform: translateY(0);
}
}
.bn-13__hint {
font-size: .78rem;
color: var(--sub);
max-width: 420px;
line-height: 1.5;
}
.bn-13__hint code {
font-family: 'JetBrains Mono',ui-monospace,monospace;
font-size: .72rem;
padding: 1px 5px;
border-radius: 4px;
background: rgba(253,242,248,.14);
}
@media (prefers-reduced-motion: reduce) {
.bn-13__word span {
animation: none!important;
transform: none!important;
}
}.bn-13 {
--page-bg: #831843;
--ink: #fdf2f8;
--sub: rgba(253,242,248,.7);
--accent: #f9a8d4;
--accent-hi: #f0abfc;
font-family: -apple-system,BlinkMacSystemFont,'Poppins','Inter','Segoe UI',sans-serif;
width: 100%;
min-height: 100vh;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 32px;
padding: 32px;
background: radial-gradient(60% 60% at 50% 40%, rgba(240,171,252,.16) 0%, transparent 60%), linear-gradient(135deg,#831843 0%,#9d174d 50%,#701a75 100%);
color: var(--ink);
text-align: center;
}
.bn-13 *,
.bn-13 *::before,
.bn-13 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.bn-13 ::selection {
background: var(--accent);
color: #831843;
}
.bn-13__label {
font-size: .9rem;
font-weight: 600;
color: var(--sub);
letter-spacing: .14em;
text-transform: uppercase;
}
.bn-13__word {
display: inline-flex;
align-items: baseline;
gap: 6px;
font-family: 'Poppins','Inter',sans-serif;
font-size: clamp(4rem,10vw,7rem);
font-weight: 900;
letter-spacing: -.03em;
line-height: 1.2;
color: var(--accent-hi);
}
.bn-13__word span {
display: inline-block;
animation: bn-13-letter 1.8s cubic-bezier(.55,0,.45,1) infinite;
will-change: transform;
}
.bn-13__word span:nth-child(1) {
animation-delay: 0s;
}
.bn-13__word span:nth-child(2) {
animation-delay: .12s;
}
.bn-13__word span:nth-child(3) {
animation-delay: .24s;
}
.bn-13__word span:nth-child(4) {
animation-delay: .36s;
}
.bn-13__word span:nth-child(5) {
animation-delay: .48s;
}
.bn-13__word span:nth-child(6) {
animation-delay: .60s;
}
@keyframes bn-13-letter {
0% {
transform: translateY(0);
}
30% {
transform: translateY(-20px);
}
60% {
transform: translateY(-8px);
}
100% {
transform: translateY(0);
}
}
.bn-13__hint {
font-size: .78rem;
color: var(--sub);
max-width: 420px;
line-height: 1.5;
}
.bn-13__hint code {
font-family: 'JetBrains Mono',ui-monospace,monospace;
font-size: .72rem;
padding: 1px 5px;
border-radius: 4px;
background: rgba(253,242,248,.14);
}
@media (prefers-reduced-motion: reduce) {
.bn-13__word span {
animation: none!important;
transform: none!important;
}
}Here's a working CSS Bounce Animation from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Bouncing Letters Wave
Source: https://codefronts.com/motion/css-bounce-animation/bouncing-letters-wave/
Individual letters in a word bounce up in sequence, creating a wave motion — the classic 404-page / playful branding pattern. Deep pink surface with the letters of "BOUNCE" spelled out, each letter animated with staggered animation-delay. Every letter runs the SAME translateY bounce keyframe but starts at a different point in the cycle, creating the wave illusion. Users search for this when building error pages, empty states, or playful hero moments.
## HTML
```html
<div class="bn-13">
<p class="bn-13__label">Bouncing letters wave</p>
<p class="bn-13__word" aria-label="Bounce">
<span aria-hidden="true">B</span>
<span aria-hidden="true">O</span>
<span aria-hidden="true">U</span>
<span aria-hidden="true">N</span>
<span aria-hidden="true">C</span>
<span aria-hidden="true">E</span>
</p>
<p class="bn-13__hint">Each letter uses the same bounce keyframe with staggered <code>animation-delay</code>.</p>
</div>
```
## CSS
```css
.bn-13 {
--page-bg: #831843;
--ink: #fdf2f8;
--sub: rgba(253,242,248,.7);
--accent: #f9a8d4;
--accent-hi: #f0abfc;
font-family: -apple-system,BlinkMacSystemFont,'Poppins','Inter','Segoe UI',sans-serif;
width: 100%;
min-height: 100vh;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 32px;
padding: 32px;
background: radial-gradient(60% 60% at 50% 40%, rgba(240,171,252,.16) 0%, transparent 60%), linear-gradient(135deg,#831843 0%,#9d174d 50%,#701a75 100%);
color: var(--ink);
text-align: center;
}
.bn-13 *,
.bn-13 *::before,
.bn-13 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.bn-13 ::selection {
background: var(--accent);
color: #831843;
}
.bn-13__label {
font-size: .9rem;
font-weight: 600;
color: var(--sub);
letter-spacing: .14em;
text-transform: uppercase;
}
.bn-13__word {
display: inline-flex;
align-items: baseline;
gap: 6px;
font-family: 'Poppins','Inter',sans-serif;
font-size: clamp(4rem,10vw,7rem);
font-weight: 900;
letter-spacing: -.03em;
line-height: 1.2;
color: var(--accent-hi);
}
.bn-13__word span {
display: inline-block;
animation: bn-13-letter 1.8s cubic-bezier(.55,0,.45,1) infinite;
will-change: transform;
}
.bn-13__word span:nth-child(1) {
animation-delay: 0s;
}
.bn-13__word span:nth-child(2) {
animation-delay: .12s;
}
.bn-13__word span:nth-child(3) {
animation-delay: .24s;
}
.bn-13__word span:nth-child(4) {
animation-delay: .36s;
}
.bn-13__word span:nth-child(5) {
animation-delay: .48s;
}
.bn-13__word span:nth-child(6) {
animation-delay: .60s;
}
@keyframes bn-13-letter {
0% {
transform: translateY(0);
}
30% {
transform: translateY(-20px);
}
60% {
transform: translateY(-8px);
}
100% {
transform: translateY(0);
}
}
.bn-13__hint {
font-size: .78rem;
color: var(--sub);
max-width: 420px;
line-height: 1.5;
}
.bn-13__hint code {
font-family: 'JetBrains Mono',ui-monospace,monospace;
font-size: .72rem;
padding: 1px 5px;
border-radius: 4px;
background: rgba(253,242,248,.14);
}
@media (prefers-reduced-motion: reduce) {
.bn-13__word span {
animation: none!important;
transform: none!important;
}
}
```Here's a working CSS Bounce Animation from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Bouncing Letters Wave
Source: https://codefronts.com/motion/css-bounce-animation/bouncing-letters-wave/
Individual letters in a word bounce up in sequence, creating a wave motion — the classic 404-page / playful branding pattern. Deep pink surface with the letters of "BOUNCE" spelled out, each letter animated with staggered animation-delay. Every letter runs the SAME translateY bounce keyframe but starts at a different point in the cycle, creating the wave illusion. Users search for this when building error pages, empty states, or playful hero moments.
## HTML
```html
<div class="bn-13">
<p class="bn-13__label">Bouncing letters wave</p>
<p class="bn-13__word" aria-label="Bounce">
<span aria-hidden="true">B</span>
<span aria-hidden="true">O</span>
<span aria-hidden="true">U</span>
<span aria-hidden="true">N</span>
<span aria-hidden="true">C</span>
<span aria-hidden="true">E</span>
</p>
<p class="bn-13__hint">Each letter uses the same bounce keyframe with staggered <code>animation-delay</code>.</p>
</div>
```
## CSS
```css
.bn-13 {
--page-bg: #831843;
--ink: #fdf2f8;
--sub: rgba(253,242,248,.7);
--accent: #f9a8d4;
--accent-hi: #f0abfc;
font-family: -apple-system,BlinkMacSystemFont,'Poppins','Inter','Segoe UI',sans-serif;
width: 100%;
min-height: 100vh;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 32px;
padding: 32px;
background: radial-gradient(60% 60% at 50% 40%, rgba(240,171,252,.16) 0%, transparent 60%), linear-gradient(135deg,#831843 0%,#9d174d 50%,#701a75 100%);
color: var(--ink);
text-align: center;
}
.bn-13 *,
.bn-13 *::before,
.bn-13 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.bn-13 ::selection {
background: var(--accent);
color: #831843;
}
.bn-13__label {
font-size: .9rem;
font-weight: 600;
color: var(--sub);
letter-spacing: .14em;
text-transform: uppercase;
}
.bn-13__word {
display: inline-flex;
align-items: baseline;
gap: 6px;
font-family: 'Poppins','Inter',sans-serif;
font-size: clamp(4rem,10vw,7rem);
font-weight: 900;
letter-spacing: -.03em;
line-height: 1.2;
color: var(--accent-hi);
}
.bn-13__word span {
display: inline-block;
animation: bn-13-letter 1.8s cubic-bezier(.55,0,.45,1) infinite;
will-change: transform;
}
.bn-13__word span:nth-child(1) {
animation-delay: 0s;
}
.bn-13__word span:nth-child(2) {
animation-delay: .12s;
}
.bn-13__word span:nth-child(3) {
animation-delay: .24s;
}
.bn-13__word span:nth-child(4) {
animation-delay: .36s;
}
.bn-13__word span:nth-child(5) {
animation-delay: .48s;
}
.bn-13__word span:nth-child(6) {
animation-delay: .60s;
}
@keyframes bn-13-letter {
0% {
transform: translateY(0);
}
30% {
transform: translateY(-20px);
}
60% {
transform: translateY(-8px);
}
100% {
transform: translateY(0);
}
}
.bn-13__hint {
font-size: .78rem;
color: var(--sub);
max-width: 420px;
line-height: 1.5;
}
.bn-13__hint code {
font-family: 'JetBrains Mono',ui-monospace,monospace;
font-size: .72rem;
padding: 1px 5px;
border-radius: 4px;
background: rgba(253,242,248,.14);
}
@media (prefers-reduced-motion: reduce) {
.bn-13__word span {
animation: none!important;
transform: none!important;
}
}
```How this works
Each letter is a separate <span> inside a container. All spans share the SAME @keyframes bn-13-letter — a 3-stop bounce cascade: 0% translateY(0) → 30% translateY(-20px) → 60% translateY(-8px) → 100% translateY(0). The two-peak pattern (big up, small up, settle) mimics a decaying ball bounce.
The staggered animation-delay on each letter creates the wave — letter 1 starts at 0s, letter 2 at 0.12s, letter 3 at 0.24s, and so on. With a 1.8s cycle and 6 letters, the total stagger (0.6s) is exactly 1/3 of the cycle — so as one letter reaches its peak, the next is 40% into its rise, the third is 20%, creating the illusion of continuous flowing motion.
The stagger formula: total-stagger < cycle-duration. If the total stagger equals or exceeds the cycle duration, letters don't overlap in phases and the wave breaks into 6 separate blinks. For 6 letters + 1.8s cycle, 0.12s per letter (0.72s total) works — under the 1.8s cycle limit but visually spread enough.
Deep magenta background with the word "BOUNCE" in a large playful sans (Poppins / Fraunces / any display font). Each letter has a slight rotation offset for organic feel. On prefers-reduced-motion, the letters stay static — the word is still visible, just not animated.
Make it yours
- Change bounce height by editing
translateY(-20px)— 12px is subtle, 32px is playful. Match to your brand's motion vocabulary. - Change the stagger delay by editing the
--imultiplier — 0.08s per letter for tight wave, 0.16s per letter for spread-out wave. Total stagger must stay under the cycle duration. - Change the word by adding/removing
<span>elements — the CSS usesnth-child(n)for delays but you can switch to a CSS custom property +--ifor arbitrary word lengths. - For colorful branding (each letter a different color), add
--huevariable per letter and usebackground: hsl(var(--hue), 70%, 60%); -webkit-background-clip: text; color: transparent. - For Tailwind: use custom keyframes +
animation-delay-[0.12s]arbitrary values on each letter. Requirestailwind.config.jskeyframes registration. See FAQ #4.
Gotchas — read before shipping
- Do NOT use inline-block on letters with animation — the bounce works fine but text baseline shifts as elements rise, which can push the surrounding text around. Use
display: inline-blockon letters AND on the container so the container reserves its full height. - For accessibility, wrap the visible letters in a
<span aria-label="Bounce">so screen readers hear the word as a whole, not letter-by-letter. Individual<span>letters can havearia-hidden="true". - The stagger delay MUST be less than the animation duration. If cycle is 1.8s and you use 0.4s per letter with 6 letters (2.4s total stagger), the last letter starts AFTER the first letter has finished its bounce — no wave motion, just sequential blinks.
- For long words (10+ letters), reduce per-letter delay to keep total stagger under cycle duration. Or use a shorter cycle duration.
- The vertical bounce compresses the container's line-height calculation. If the surrounding UI depends on precise text baseline (e.g., sitting on a line rule), the bouncing letters will break the alignment. Reserve fixed height on the container.
- For platforms with LTR + RTL support, the animation-delay progression works the same in both directions — but if you're using directional wording ("forward," "onwards"), the wave may read differently in RTL contexts.
- For
prefers-reduced-motion, disable all bounces — the letters stay static. The word is still visible; only decorative wave motion is disabled.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 45+ | 10+ | 40+ | 45+ |
transform + @keyframes + animation-delay. Universal, GPU-accelerated.