About this snippet

Delight shoppers with this interactive button. When clicked, it morphs into a loading spinner and then a success checkmark.

Size
3.1 KB
Uses
HTML, CSS, JavaScript
Loads
Nothing external
Views
543
<button class="cart-btn">
  <span class="btn-content">
    <span class="btn-text">Add to Cart</span>
    <svg class="icon-cart" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
      <circle cx="9" cy="21" r="1"></circle>
      <circle cx="20" cy="21" r="1"></circle>
      <path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
    </svg>
  </span>
  <div class="loader"></div>
  <svg class="icon-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">
    <polyline points="20 6 9 17 4 12"></polyline>
  </svg>
</button>
* { box-sizing: border-box; }
body {
  font-family: system-ui, "Segoe UI", sans-serif;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  background: #f1f5f9;
}

.cart-btn {
  position: relative;
  background: #1e293b;
  color: white;
  border: none;
  border-radius: 12px;
  padding: 14px 28px;
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
  overflow: hidden;
  transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 160px;
  height: 54px;
  box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1);
}

.cart-btn:hover {
  background: #334155;
  transform: translateY(-2px);
  box-shadow: 0 10px 15px -3px rgba(0,0,0,0.15);
}

.btn-content {
  display: flex;
  align-items: center;
  gap: 10px;
  transition: opacity 0.3s, transform 0.3s;
}

.icon-cart {
  width: 20px;
  height: 20px;
}

.loader {
  display: none;
  width: 24px;
  height: 24px;
  border: 3px solid rgba(255,255,255,0.3);
  border-top-color: white;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
  position: absolute;
}

.icon-check {
  display: none;
  width: 28px;
  height: 28px;
  color: white;
  position: absolute;
  stroke-dasharray: 24;
  stroke-dashoffset: 24;
  animation: drawCheck 0.4s forwards 0.1s;
}

/* STATE: Loading */
.cart-btn.is-loading {
  width: 54px; /* Collapse to circle */
  min-width: 54px;
  border-radius: 50%;
  background: #334155;
  cursor: default;
  transform: none;
  padding: 0;
}

.cart-btn.is-loading .btn-content {
  opacity: 0;
  transform: scale(0.5);
}

.cart-btn.is-loading .loader {
  display: block;
}

/* STATE: Added */
.cart-btn.is-added {
  width: 54px;
  min-width: 54px;
  border-radius: 50%;
  background: #22c55e; /* Green */
  cursor: default;
  padding: 0;
}

.cart-btn.is-added .btn-content,
.cart-btn.is-added .loader {
  display: none;
}

.cart-btn.is-added .icon-check {
  display: block;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

@keyframes drawCheck {
  to { stroke-dashoffset: 0; }
}
const btn = document.querySelector(".cart-btn");

btn.addEventListener("click", () => {
  // Prevent multiple clicks
  if (btn.classList.contains("is-loading") || btn.classList.contains("is-added")) return;

  // 1. Start Loading
  btn.classList.add("is-loading");

  // Simulate API request (1.5s)
  setTimeout(() => {
    // 2. Success State
    btn.classList.remove("is-loading");
    btn.classList.add("is-added");

    // 3. Reset after 2.5s so user can add again
    setTimeout(() => {
      btn.classList.remove("is-added");
    }, 2500);
  }, 1500);
});
Esc
↑↓ moveEnter select

Start from a template

Open an .html file, or HTML, CSS, JS, Markdown, Less or TypeScript files. You can also drop them here. Styles and scripts go into their panes.

Share

Source

Keyboard shortcuts

Save