Success Check

A ring draws itself closed, the check mark strokes in, and the badge gives one small pop to say the job is done.

  • CSS
  • SVG & shapes
  • Entrance
  • Added

Payment complete

AI prompt

Create an animated success badge in SVG and CSS. Draw a circle (r 26) and a check path inside a 64px viewBox, both with stroke only. Set each stroke-dasharray to its path length and stroke-dashoffset to the same value. Animate the circle offset to 0 over 600ms with cubic-bezier(0.65, 0, 0.35, 1), then the check offset to 0 over 300ms starting at 550ms, then fill the circle by scaling a filled disc from 0 to 1 over 260ms at 450ms so the check strokes in on top of it, and finally pop the whole badge with scale 1 → 1.1 → 1 over 300ms at 850ms. Use animation-fill-mode both so it rests in the finished state. Add a Replay button that remounts the SVG with a new key. Under prefers-reduced-motion, show the finished badge. The caption reads "Payment complete".

Source

Demo.tsx
'use client'

import { useState } from 'react'
import s from './demo.module.css'

export default function SuccessCheck({ labels }: { labels: Record<string, string> }) {
  const [run, setRun] = useState(0)

  return (
    <div className={s.wrap}>
      <svg key={run} className={s.badge} viewBox="0 0 64 64" aria-hidden="true">
        <circle className={s.disc} cx="32" cy="32" r="26" />
        <circle className={s.ring} cx="32" cy="32" r="26" />
        <path className={s.check} d="M21 33l7.5 7.5L44 25" />
      </svg>
      <p className={s.caption}>{labels.caption}</p>
      <button type="button" className={s.replay} onClick={() => setRun((n) => n + 1)}>
        {labels.replay}
      </button>
    </div>
  )
}
demo.module.css
.wrap {
  display: grid;
  justify-items: center;
  gap: 10px;
}

.badge {
  width: 72px;
  height: 72px;
  animation: pop 300ms var(--ease-out) 850ms both;
}

.ring, .check {
  fill: none;
  stroke: var(--stage-accent);
  stroke-width: 3.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.ring {
  stroke-dasharray: 164;
  stroke-dashoffset: 164;
  transform: rotate(-90deg);
  transform-origin: center;
  animation: draw 600ms var(--ease-in-out) both;
}

.check {
  stroke: var(--stage-accent-ink);
  stroke-dasharray: 36;
  stroke-dashoffset: 36;
  animation: draw 300ms var(--ease-out) 550ms both;
}

.disc {
  fill: var(--stage-accent);
  transform: scale(0);
  transform-origin: center;
  animation: grow 260ms var(--ease-out) 450ms both;
}

@keyframes draw { to { stroke-dashoffset: 0; } }
@keyframes grow { to { transform: scale(1); } }
@keyframes pop { 50% { transform: scale(1.1); } }

.caption {
  margin: 0 0 6px;
  font: 600 var(--text-base) / 1.2 var(--font-display);
  color: var(--stage-ink);
}

.replay {
  height: 32px;
  padding: 0 12px;
  border: 1px solid var(--stage-rule);
  border-radius: var(--radius-sm);
  background: var(--stage-surface);
  color: var(--stage-ink);
  font: 500 var(--text-sm) / 1 var(--font-body);
  white-space: nowrap;
  cursor: pointer;
}

.replay:hover { border-color: var(--stage-accent); }
.replay:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  .badge, .ring, .check, .disc { animation: none; }
  .ring, .check { stroke-dashoffset: 0; }
  .disc { transform: none; }
}

Suggest an idea

Seen a motion or a theme you would like to find here? Describe it, and it opens as a prefilled GitHub issue for you to post.

What are you suggesting?

Needs a GitHub account. Suggestions are public.