spb/svgarden Public
SVGarden — searchable bank of 74 self-contained SVG+CSS animation snippets (svgarden.dev)
HTML 79.2%
Astro 10.3%
JavaScript 6%
CSS 3.7%
Shell 0.8%
1<!--2 ============================================================3 SVGarden — https://www.svgarden.dev4 Author : Simon-Pierre Boucher5 Contact: contact@spboucher.ai6 File : snippets/buttons/btn-success-morph.html7 Desc : Click morphs the button into a spinner, then a success check8 ============================================================9-->10<!--svgarden11title: Success-morph button12slug: btn-success-morph13category: buttons14tags: [button, state-machine, spinner, success, js]15difficulty: advanced16techniques: [data-state-attribute, width-morph, pathLength, animation-choreography]17how_it_works: >18 The button is a three-state machine driven entirely by a data-state attribute19 — idle, loading, done — and the script's only job is to advance that attribute20 (with one setTimeout standing in for a real request). Every visual is CSS21 keyed off [data-state]: the pill's max-width collapses until it equals its22 height and the border-radius makes it a circle, the spinner arc fades in and23 spins, then the checkmark draws itself with the pathLength trick while the24 background settles green. Keeping state in an attribute instead of classes25 means the CSS reads like a truth table and impossible combinations simply26 cannot exist.27customizable:28 - { var: "--sg-color", label: "Base color", type: color, default: "#7F77DD" }29 - { var: "--sg-success", label: "Success color", type: color, default: "#2BA05A" }30 - { var: "--sg-size", label: "Font size", type: range, min: 12, max: 24, default: 16, unit: px }31created: 2026-08-1032-->33<button class="sg-btn-success-morph" type="button" data-state="idle" style="--sg-color:#7F77DD; --sg-success:#2BA05A; --sg-size:16px;">34 <span class="sg-btn-success-morph-label">Save changes</span>35 <svg viewBox="0 0 28 28" aria-hidden="true" focusable="false">36 <circle class="sg-btn-success-morph-arc" cx="14" cy="14" r="10" pathLength="100" />37 <path class="sg-btn-success-morph-check" d="M8 14.5 L12.5 19 L20 9.5" pathLength="100" />38 </svg>39</button>40<script>41 document.querySelectorAll('.sg-btn-success-morph:not([data-sg-init])').forEach(function (btn) {42 btn.setAttribute('data-sg-init', '');43 btn.addEventListener('click', function () {44 var state = btn.getAttribute('data-state');45 if (state === 'idle') {46 btn.setAttribute('data-state', 'loading');47 setTimeout(function () { btn.setAttribute('data-state', 'done'); }, 1400);48 } else if (state === 'done') {49 btn.setAttribute('data-state', 'idle');50 }51 });52 });53</script>54<style>55 .sg-btn-success-morph {56 appearance: none;57 border: 0;58 font: inherit;59 font-size: var(--sg-size);60 font-weight: 600;61 color: #ffffff;62 background: var(--sg-color);63 height: 3em;64 max-width: 12em;65 padding: 0 1.8em;66 border-radius: 1.5em;67 cursor: pointer;68 position: relative;69 overflow: hidden;70 white-space: nowrap;71 transition: max-width 0.45s cubic-bezier(0.6, 0, 0.3, 1), padding 0.45s ease, background 0.4s ease;72 }73 .sg-btn-success-morph-label {74 transition: opacity 0.2s ease;75 }76 .sg-btn-success-morph svg {77 position: absolute;78 inset: 0;79 margin: auto;80 width: 1.9em;81 height: 1.9em;82 }83 .sg-btn-success-morph-arc,84 .sg-btn-success-morph-check {85 fill: none;86 stroke: #ffffff;87 stroke-width: 2.5;88 stroke-linecap: round;89 stroke-linejoin: round;90 }91 .sg-btn-success-morph-arc {92 stroke-dasharray: 28 72;93 opacity: 0;94 transform-box: fill-box;95 transform-origin: center;96 }97 .sg-btn-success-morph-check {98 stroke-dasharray: 100;99 stroke-dashoffset: 100;100 }101 .sg-btn-success-morph[data-state='loading'] {102 max-width: 3em;103 padding: 0;104 cursor: progress;105 }106 .sg-btn-success-morph[data-state='loading'] .sg-btn-success-morph-label,107 .sg-btn-success-morph[data-state='done'] .sg-btn-success-morph-label {108 opacity: 0;109 }110 .sg-btn-success-morph[data-state='loading'] .sg-btn-success-morph-arc {111 opacity: 1;112 animation: sg-btn-success-morph-spin 0.8s linear infinite;113 }114 .sg-btn-success-morph[data-state='done'] {115 max-width: 3em;116 padding: 0;117 background: var(--sg-success);118 }119 .sg-btn-success-morph[data-state='done'] .sg-btn-success-morph-check {120 animation: sg-btn-success-morph-draw 0.45s ease-out 0.1s forwards;121 }122 @keyframes sg-btn-success-morph-spin {123 to { transform: rotate(360deg); }124 }125 @keyframes sg-btn-success-morph-draw {126 to { stroke-dashoffset: 0; }127 }128</style>129