16 CSS Bounce Animations03 / 16
Success Checkmark Pop
A payment / form-submit success confirmation — mint circle scales up with elastic overshoot, then the checkmark draws in via stroke-dashoffset. Stripe checkout / Apple Pay / Shopify payment success aesthetic (mint #d1fae5 success surface + emerald #065f46 check). The bounce reads as "you're good." The classic two-stage cascade — circle pops, then check draws — that every well-designed confirmation UI uses.
Published
The code
<div class="bn-03" role="status" aria-live="polite">
<span class="bn-03__circle" aria-hidden="true">
<svg viewBox="0 0 52 52" width="72" height="72" aria-hidden="true">
<path class="bn-03__check-path" d="M14 27 l8 8 l16 -18" fill="none" stroke="currentColor" stroke-width="4.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
<p class="bn-03__label">Payment received</p>
</div><div class="bn-03" role="status" aria-live="polite">
<span class="bn-03__circle" aria-hidden="true">
<svg viewBox="0 0 52 52" width="72" height="72" aria-hidden="true">
<path class="bn-03__check-path" d="M14 27 l8 8 l16 -18" fill="none" stroke="currentColor" stroke-width="4.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
<p class="bn-03__label">Payment received</p>
</div>.bn-03 {
--page-bg: #f0fdf4;
--ink: #052e1a;
--sub: #4b6b58;
--mint: #d1fae5;
--emerald: #065f46;
--emerald-soft: rgba(6,95,70,.24);
font-family: -apple-system,BlinkMacSystemFont,'SF Pro Rounded','Inter','Segoe UI',sans-serif;
width: 100%;
min-height: 100vh;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 24px;
padding: 32px;
background: radial-gradient(60% 60% at 50% 50%, rgba(16,185,129,.12) 0%, transparent 60%), var(--page-bg);
color: var(--ink);
text-align: center;
}
.bn-03 *,
.bn-03 *::before,
.bn-03 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.bn-03 ::selection {
background: var(--emerald);
color: #fff;
}
.bn-03__circle {
display: inline-flex;
align-items: center;
justify-content: center;
width: 120px;
height: 120px;
border-radius: 50%;
background: var(--mint);
color: var(--emerald);
transform: scale(0);
animation: bn-03-pop .5s cubic-bezier(.34,1.56,.64,1) forwards;
box-shadow: 0 20px 40px -10px var(--emerald-soft);
}
@keyframes bn-03-pop {
0% {
transform: scale(0);
}
40% {
transform: scale(1.15);
}
70% {
transform: scale(.95);
}
100% {
transform: scale(1);
}
}
.bn-03__check-path {
stroke-dasharray: 44;
stroke-dashoffset: 44;
animation: bn-03-draw .4s cubic-bezier(.65,0,.35,1) .3s forwards;
}
@keyframes bn-03-draw {
to {
stroke-dashoffset: 0;
}
}
.bn-03__label {
font-size: 1.1rem;
font-weight: 800;
letter-spacing: -.01em;
color: var(--ink);
opacity: 0;
animation: bn-03-fade .3s ease-out .55s forwards;
}
@keyframes bn-03-fade {
from {
opacity: 0;
transform: translateY(6px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (prefers-reduced-motion: reduce) {
.bn-03__circle,
.bn-03__check-path,
.bn-03__label {
animation: none!important;
}
.bn-03__circle {
transform: scale(1)!important;
}
.bn-03__check-path {
stroke-dashoffset: 0!important;
}
.bn-03__label {
opacity: 1!important;
transform: none!important;
}
}.bn-03 {
--page-bg: #f0fdf4;
--ink: #052e1a;
--sub: #4b6b58;
--mint: #d1fae5;
--emerald: #065f46;
--emerald-soft: rgba(6,95,70,.24);
font-family: -apple-system,BlinkMacSystemFont,'SF Pro Rounded','Inter','Segoe UI',sans-serif;
width: 100%;
min-height: 100vh;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 24px;
padding: 32px;
background: radial-gradient(60% 60% at 50% 50%, rgba(16,185,129,.12) 0%, transparent 60%), var(--page-bg);
color: var(--ink);
text-align: center;
}
.bn-03 *,
.bn-03 *::before,
.bn-03 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.bn-03 ::selection {
background: var(--emerald);
color: #fff;
}
.bn-03__circle {
display: inline-flex;
align-items: center;
justify-content: center;
width: 120px;
height: 120px;
border-radius: 50%;
background: var(--mint);
color: var(--emerald);
transform: scale(0);
animation: bn-03-pop .5s cubic-bezier(.34,1.56,.64,1) forwards;
box-shadow: 0 20px 40px -10px var(--emerald-soft);
}
@keyframes bn-03-pop {
0% {
transform: scale(0);
}
40% {
transform: scale(1.15);
}
70% {
transform: scale(.95);
}
100% {
transform: scale(1);
}
}
.bn-03__check-path {
stroke-dasharray: 44;
stroke-dashoffset: 44;
animation: bn-03-draw .4s cubic-bezier(.65,0,.35,1) .3s forwards;
}
@keyframes bn-03-draw {
to {
stroke-dashoffset: 0;
}
}
.bn-03__label {
font-size: 1.1rem;
font-weight: 800;
letter-spacing: -.01em;
color: var(--ink);
opacity: 0;
animation: bn-03-fade .3s ease-out .55s forwards;
}
@keyframes bn-03-fade {
from {
opacity: 0;
transform: translateY(6px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (prefers-reduced-motion: reduce) {
.bn-03__circle,
.bn-03__check-path,
.bn-03__label {
animation: none!important;
}
.bn-03__circle {
transform: scale(1)!important;
}
.bn-03__check-path {
stroke-dashoffset: 0!important;
}
.bn-03__label {
opacity: 1!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: Success Checkmark Pop
Source: https://codefronts.com/motion/css-bounce-animation/success-checkmark-pop/
A payment / form-submit success confirmation — mint circle scales up with elastic overshoot, then the checkmark draws in via stroke-dashoffset. Stripe checkout / Apple Pay / Shopify payment success aesthetic (mint #d1fae5 success surface + emerald #065f46 check). The bounce reads as "you're good." The classic two-stage cascade — circle pops, then check draws — that every well-designed confirmation UI uses.
## HTML
```html
<div class="bn-03" role="status" aria-live="polite">
<span class="bn-03__circle" aria-hidden="true">
<svg viewBox="0 0 52 52" width="72" height="72" aria-hidden="true">
<path class="bn-03__check-path" d="M14 27 l8 8 l16 -18" fill="none" stroke="currentColor" stroke-width="4.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
<p class="bn-03__label">Payment received</p>
</div>
```
## CSS
```css
.bn-03 {
--page-bg: #f0fdf4;
--ink: #052e1a;
--sub: #4b6b58;
--mint: #d1fae5;
--emerald: #065f46;
--emerald-soft: rgba(6,95,70,.24);
font-family: -apple-system,BlinkMacSystemFont,'SF Pro Rounded','Inter','Segoe UI',sans-serif;
width: 100%;
min-height: 100vh;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 24px;
padding: 32px;
background: radial-gradient(60% 60% at 50% 50%, rgba(16,185,129,.12) 0%, transparent 60%), var(--page-bg);
color: var(--ink);
text-align: center;
}
.bn-03 *,
.bn-03 *::before,
.bn-03 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.bn-03 ::selection {
background: var(--emerald);
color: #fff;
}
.bn-03__circle {
display: inline-flex;
align-items: center;
justify-content: center;
width: 120px;
height: 120px;
border-radius: 50%;
background: var(--mint);
color: var(--emerald);
transform: scale(0);
animation: bn-03-pop .5s cubic-bezier(.34,1.56,.64,1) forwards;
box-shadow: 0 20px 40px -10px var(--emerald-soft);
}
@keyframes bn-03-pop {
0% {
transform: scale(0);
}
40% {
transform: scale(1.15);
}
70% {
transform: scale(.95);
}
100% {
transform: scale(1);
}
}
.bn-03__check-path {
stroke-dasharray: 44;
stroke-dashoffset: 44;
animation: bn-03-draw .4s cubic-bezier(.65,0,.35,1) .3s forwards;
}
@keyframes bn-03-draw {
to {
stroke-dashoffset: 0;
}
}
.bn-03__label {
font-size: 1.1rem;
font-weight: 800;
letter-spacing: -.01em;
color: var(--ink);
opacity: 0;
animation: bn-03-fade .3s ease-out .55s forwards;
}
@keyframes bn-03-fade {
from {
opacity: 0;
transform: translateY(6px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (prefers-reduced-motion: reduce) {
.bn-03__circle,
.bn-03__check-path,
.bn-03__label {
animation: none!important;
}
.bn-03__circle {
transform: scale(1)!important;
}
.bn-03__check-path {
stroke-dashoffset: 0!important;
}
.bn-03__label {
opacity: 1!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: Success Checkmark Pop
Source: https://codefronts.com/motion/css-bounce-animation/success-checkmark-pop/
A payment / form-submit success confirmation — mint circle scales up with elastic overshoot, then the checkmark draws in via stroke-dashoffset. Stripe checkout / Apple Pay / Shopify payment success aesthetic (mint #d1fae5 success surface + emerald #065f46 check). The bounce reads as "you're good." The classic two-stage cascade — circle pops, then check draws — that every well-designed confirmation UI uses.
## HTML
```html
<div class="bn-03" role="status" aria-live="polite">
<span class="bn-03__circle" aria-hidden="true">
<svg viewBox="0 0 52 52" width="72" height="72" aria-hidden="true">
<path class="bn-03__check-path" d="M14 27 l8 8 l16 -18" fill="none" stroke="currentColor" stroke-width="4.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
<p class="bn-03__label">Payment received</p>
</div>
```
## CSS
```css
.bn-03 {
--page-bg: #f0fdf4;
--ink: #052e1a;
--sub: #4b6b58;
--mint: #d1fae5;
--emerald: #065f46;
--emerald-soft: rgba(6,95,70,.24);
font-family: -apple-system,BlinkMacSystemFont,'SF Pro Rounded','Inter','Segoe UI',sans-serif;
width: 100%;
min-height: 100vh;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 24px;
padding: 32px;
background: radial-gradient(60% 60% at 50% 50%, rgba(16,185,129,.12) 0%, transparent 60%), var(--page-bg);
color: var(--ink);
text-align: center;
}
.bn-03 *,
.bn-03 *::before,
.bn-03 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.bn-03 ::selection {
background: var(--emerald);
color: #fff;
}
.bn-03__circle {
display: inline-flex;
align-items: center;
justify-content: center;
width: 120px;
height: 120px;
border-radius: 50%;
background: var(--mint);
color: var(--emerald);
transform: scale(0);
animation: bn-03-pop .5s cubic-bezier(.34,1.56,.64,1) forwards;
box-shadow: 0 20px 40px -10px var(--emerald-soft);
}
@keyframes bn-03-pop {
0% {
transform: scale(0);
}
40% {
transform: scale(1.15);
}
70% {
transform: scale(.95);
}
100% {
transform: scale(1);
}
}
.bn-03__check-path {
stroke-dasharray: 44;
stroke-dashoffset: 44;
animation: bn-03-draw .4s cubic-bezier(.65,0,.35,1) .3s forwards;
}
@keyframes bn-03-draw {
to {
stroke-dashoffset: 0;
}
}
.bn-03__label {
font-size: 1.1rem;
font-weight: 800;
letter-spacing: -.01em;
color: var(--ink);
opacity: 0;
animation: bn-03-fade .3s ease-out .55s forwards;
}
@keyframes bn-03-fade {
from {
opacity: 0;
transform: translateY(6px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (prefers-reduced-motion: reduce) {
.bn-03__circle,
.bn-03__check-path,
.bn-03__label {
animation: none!important;
}
.bn-03__circle {
transform: scale(1)!important;
}
.bn-03__check-path {
stroke-dashoffset: 0!important;
}
.bn-03__label {
opacity: 1!important;
transform: none!important;
}
}
```How this works
The circle uses transform: scale(0) initially and animates to scale(1) via @keyframes bn-03-pop with 4 stops — 0% scale(0) → 40% scale(1.15) → 70% scale(.95) → 100% scale(1). Duration 500ms with cubic-bezier(.34, 1.56, .64, 1) — the elastic overshoot cubic-bezier that's become the industry standard for "success" moments (Stripe, Shopify, Apple Pay).
The checkmark is a pure SVG <path> drawn with stroke-dasharray + stroke-dashoffset — starts hidden (offset = full path length), animates to stroke-dashoffset: 0 over 400ms AFTER the circle pops (via animation-delay: .3s). This creates the classic "circle appears, then check draws itself in" cascade.
The demo shows the check as the hero at 120px, with a single-line label "Payment received" below it — enough to signal the use case without a full receipt/order-number/CTA layout burying the effect.
Make it yours
- Change color for different success types — mint for payment/order (default), sky-blue for account created, purple for magic-link sent, amber for saved-to-wishlist. Match to your semantic color palette.
- For high-stakes contexts (medical procedure booking, legal signature) reduce overshoot — replace
cubic-bezier(.34, 1.56, .64, 1)withcubic-bezier(.22, 1, .36, 1)(no overshoot, smooth deceleration). Elastic bounces feel wrong on serious confirmations. - Add confetti burst around the checkmark for order-success moments — pure CSS with 12
<span>elements at radial angles +--angle/--distCSS variables. See css-modals collection Demo 15 for the pattern. - For Tailwind:
animate-[bn03pop_.5s_cubic-bezier(.34,1.56,.64,1)_both]+ register keyframes intailwind.config.js. See FAQ #4. - Chain a follow-up scale-and-drift-up on the copy below the check — start opacity 0 +
translateY(8px)then fade+rise into position viaanimation-delay: .5s. Creates a mini-cascade of "check pops in, then text arrives."
Gotchas — read before shipping
- The SVG check draw MUST use
stroke-dashoffset— NOTwidth,height, ordattribute animation.stroke-dashoffsetis a compositor-safe property;dattribute animation triggers full re-paint of the path on every frame. - The circle pop and check draw must CASCADE — check draws AFTER circle pops. If both animate at the same time, users see a partial check inside a growing circle which reads as broken. Use
animation-delay: .3son the check to gate it. - For accessibility, announce the success via
aria-live='polite'on a wrapper — screen-reader users can't see the checkmark. "Payment successful" is the required announcement pattern. - The 1.15 overshoot scale is INTENTIONAL — a smooth 0-to-1 scale feels flat and mechanical. The 15% overshoot is what makes it read as "physical arrival with delight" instead of "UI blipping into existence."
- Do NOT loop this animation — success confirmations are one-shot events. If your CSS accidentally loops (missing
animation-fill-mode: forwardsoriteration-count: 1), the check pulses forever which reads as "trying to succeed" instead of "succeeded." - For payment context specifically, this UI must appear ONLY after the backend confirms the payment intent — never optimistically. Showing success before Stripe's
confirmCardPaymentresolves creates "I paid but was charged twice" support tickets. - For
prefers-reduced-motion, skip the scale bounce + check draw — show the checkmark in its final state instantly. The information (success) is preserved; only the celebration motion is disabled.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 45+ | 10+ | 40+ | 45+ |
transform + stroke-dashoffset + @keyframes. Universal, GPU-accelerated.