← Back to gallery
SVG

Success Checkmark Path Draw

Success checkmark drawn as an SVG path via stroke-dasharray animation, with an optional ring outline that completes around the same time. Three success cadences: a quick confirmation, a celebratory pulse, and a slow editorial draw.

svg-pathcheckmarkstroke-dasharraystroke-dashoffsetsuccess-feedbackaria-liveprefers-reduced-motion

Feedback · SVG path draw · stroke-dasharray

Success Checkmark Path Draw

Two success-checkmark draw treatments — a Ring + Tick sequential draw (the canonical success affirmation: ring strokes first, tick follows) and a Tick-only with a final scale pop (a denser inline confirmation). Both drive the draw via stroke-dasharray + stroke-dashoffset measured at mount with getTotalLength() so the dash math stays sharp across viewports. Stage cards loop the draw via CSS @keyframes synced with the gallery thumbnail; inspector preview adds an explicit Replay control.

Sequential · ring first, tick follows

Ring + Tick Sequential

A ring strokes first, then the tick rides the trailing handoff. The canonical success affirmation — used after form submits, payment confirmations, and any "we got it" moment that benefits from a slight pause. getTotalLength() reads each path at mount so the dash math stays sharp across viewports.

  • ring + tick
  • sequential
  • getTotalLength

Dense · tick draw + a scale pop

Tick + Scale Pop

A naked tick path draws in quickly and closes with a subtle scale pop from the path midpoint. Used as a dense confirmation where a full ring would feel oversized — the pop gives the icon a final beat without the visual cost of a ring.

  • tick-only
  • scale pop
  • dense

Success checkmark inspector

Ring + Tick Sequential

click replay
  • ring + tick
  • sequential
  • getTotalLength

A ring strokes first, then the tick rides the trailing handoff. The canonical success affirmation — used after form submits, payment confirmations, and any "we got it" moment that benefits from a slight pause. getTotalLength() reads each path at mount so the dash math stays sharp across viewports.

Helped you ship something? 🐟 Send my cat a churu

/* Ring + tick sequential — ring strokes first, tick rides the trailing handoff. */
.success-check__ring,
.success-check__tick {
  fill: none;
  stroke: #34d399;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 3;
}

/* dash arrays come from getTotalLength() at mount */
.success-check__ring {
  stroke-dasharray: var(--ring-length);
  stroke-dashoffset: var(--ring-length);
  animation: drawRing 420ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
}

.success-check__tick {
  stroke-dasharray: var(--tick-length);
  stroke-dashoffset: var(--tick-length);
  animation: drawTick 280ms cubic-bezier(0.22, 1, 0.36, 1) 340ms forwards;
}

@keyframes drawRing { to { stroke-dashoffset: 0; } }
@keyframes drawTick { to { stroke-dashoffset: 0; } }

@media (prefers-reduced-motion: reduce) {
  .success-check__ring,
  .success-check__tick { animation: none; stroke-dashoffset: 0; }
}

How to make this

A success check path draw hides normalized SVG strokes with dashoffset, draws the ring first, then hands off to the tick while text announces the result.

html
1<div class="check-recipe" role="status" aria-live="polite">2  <svg class="check-recipe__icon" viewBox="0 0 64 64" aria-hidden="true">3    <circle class="check-recipe__ring" pathLength="1" cx="32" cy="32" r="25" />    <path class="check-recipe__tick" pathLength="1" d="M19 34 L29 44 L46 24" />  </svg>  <span>Saved</span></div> <style>.check-recipe {  display: inline-flex;  align-items: center;  gap: .75rem;  color: #dcfce7;  font: 700 1rem/1.2 ui-sans-serif, system-ui;}.check-recipe__icon {  width: 4rem;  height: 4rem;  overflow: visible;}.check-recipe__ring,23.check-recipe__tick {  fill: none;  stroke: #34d399;  stroke-linecap: round;  stroke-linejoin: round;  stroke-dasharray: 1;  stroke-dashoffset: 1;  filter: drop-shadow(0 0 6px rgba(52, 211, 153, .38));}.check-recipe__ring {  stroke-width: 3;34  animation: success-check-recipe-ring 420ms cubic-bezier(.22,1,.36,1) forwards;}.check-recipe__tick {  stroke-width: 4;38  animation: success-check-recipe-tick 280ms cubic-bezier(.22,1,.36,1) 340ms forwards;}@keyframes success-check-recipe-ring {  to { stroke-dashoffset: 0; }}@keyframes success-check-recipe-tick {  to { stroke-dashoffset: 0; }}@media (prefers-reduced-motion: reduce) {47  .check-recipe__ring,  .check-recipe__tick {    animation: none;    stroke-dashoffset: 0;  }}</style>

Annotated snippet

  1. Line 1The status text is outside the SVG and uses role=status. The animation confirms the state visually; it is not the accessible announcement by itself.
    PitfallShould the SVG itself announce success?

    No. Keep the SVG decorative and announce the state with adjacent text, role=status, aria-live, or the control state that changed. The path draw is feedback, not the accessible message.

  2. Line 2The SVG is aria-hidden because the adjacent status text already names the result. Avoid making assistive technology parse decorative path geometry.
    PitfallShould the SVG itself announce success?

    No. Keep the SVG decorative and announce the state with adjacent text, role=status, aria-live, or the control state that changed. The path draw is feedback, not the accessible message.

  3. Line 3pathLength="1" normalizes the circle and tick so dasharray: 1 can mean one full contour for both paths.
    PitfallWhy use pathLength instead of hard-coded dash lengths?

    pathLength="1" makes dash math portable across viewBox scaling and different path shapes. For complex production icons, measuring getTotalLength() in JavaScript also works when you need exact path-specific values.

  4. Line 23Ring and tick share the stroke setup. Both start with dasharray and dashoffset at 1, so the full path is hidden before the draw begins.

    Same keyframe (to { stroke-dashoffset: 0 }) on both. Forget the initial dashoffset: 1 and the path is already fully visible from frame 0 — the animation runs but has nothing to reveal.

    PitfallWhy use pathLength instead of hard-coded dash lengths?

    pathLength="1" makes dash math portable across viewBox scaling and different path shapes. For complex production icons, measuring getTotalLength() in JavaScript also works when you need exact path-specific values.

  5. Line 34The ring draws first. That creates the success container before the tick arrives, which reads cleaner than drawing both strokes at once.

    Starting both paths together feels noisy; delaying the tick gives the icon a clear confirmation beat.

    PitfallWhy sequence the ring before the tick?

    The ring establishes the container and the tick confirms the outcome. Drawing both at once can look like random stroke movement, especially at small icon sizes.

  6. Line 38The tick has a delay, but its duration is shorter. It should feel like the handoff, not a second unrelated animation.
    PitfallWhy sequence the ring before the tick?

    The ring establishes the container and the tick confirms the outcome. Drawing both at once can look like random stroke movement, especially at small icon sizes.

  7. Line 47Reduced motion skips both stroke draws and renders the completed success state immediately.
    PitfallWhich browsers support SVG pathLength and stroke dash drawing?

    Modern Chromium, Firefox, and Safari support SVG stroke dash animation and pathLength on common geometry. If pathLength behaves differently on a complex path, fall back to measured getTotalLength values.

Other pitfalls

Are SVG stroke draw animations expensive?
Small icons are usually fine. Avoid looping many large paths, combining dash animation with heavy filters, or redrawing complex illustrations in dense lists.

Notes

Overview

Success checkmarks draw their stroke from one endpoint to the other via stroke-dasharray + stroke-dashoffset animation, so the line physically renders in front of the user’s eyes rather than fading in. An optional ring outline completes around the check so the success state feels resolved. Three cadences ship: quick confirmation, celebratory pulse, slow editorial draw.

When to use it

Reach for the check-draw on form submission success, async-task completion, multi-step wizard final-step affordances. Skip it on background operations the user did not explicitly initiate — the draw demands attention that uninitiated completions do not deserve. Skip it for validation feedback that fires repeatedly during typing.

How it works

The SVG path defining the check has its stroke-dasharray set to the path’s own length (measured via path.getTotalLength() or hard-coded after inspection). The stroke-dashoffset starts at that same length (effectively pushing the dash off the visible path so nothing renders) and animates to zero, revealing the stroke endpoint-to-endpoint. The ring uses the same trick on a circle path. The celebratory variant adds a brief scale keyframe on the wrapper that lands synchronously with the check completion.

Production gotchas

Hard-coding the path length is fragile — any future redesign that shifts a control point silently breaks the animation (dash too long = check completes early, too short = check pauses mid-draw). Measure once at runtime with path.getTotalLength() or set both dasharray and dashoffset via JS for safety. Stroke endings depend on stroke-linecap: round on most check designs — without it the tip of the check stops flat and looks like a print error. Don’t animate stroke-dasharray itself, only stroke-dashoffset; animating both can confuse the compositor.

Accessibility

The check is decorative — pair it with a role="status" live region announcing the success message (“Saved”, “Payment complete”) so screen-reader users get the same confirmation. Set aria-hidden="true" on the SVG so it does not interrupt focus order. Under prefers-reduced-motion: reduce jump the dashoffset to zero in a single frame — the completed check still appears but without the drawing motion.

References

Implementation depth

The checkmark draw should confirm an already completed action, not delay completion. Use stroke-dashoffset for the visual trace while aria-live or visible status text communicates success immediately.

Keep the final state persistent. If the checkmark disappears too quickly, users may miss the confirmation; reduced motion should show the completed check and message without replaying the stroke animation.