Skip to content

Save Heartbeat

A copy-paste micro-interactions component in pure HTML, CSS & vanilla JS. Zero dependencies, framework-agnostic, MIT-licensed.

Micro-interactionsHTMLCSSJavaScriptany framework

Copy into your project

HTML
<button class="nuda-save-heartbeat" aria-label="Save" aria-pressed="false">
  <svg viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
    <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/>
  </svg>
</button>
CSS
/* Save Heartbeat — heart fills and double-beats on save. */
.nuda-save-heartbeat {
  background: none;
  border: none;
  cursor: pointer;
  padding: 8px;
  outline: none;
}

.nuda-save-heartbeat svg {
  width: 28px;
  height: 28px;
  fill: none;
  stroke: #777;
  transition: fill 0.25s, stroke 0.25s;
}

.nuda-save-heartbeat[aria-pressed="true"] svg {
  fill: #e4ff54;
  stroke: #e4ff54;
}

.nuda-save-heartbeat.is-beat svg {
  animation: nuda-save-beat 0.7s ease-in-out;
}

@keyframes nuda-save-beat {
  0%   { transform: scale(1); }
  15%  { transform: scale(1.3); }
  30%  { transform: scale(1); }
  45%  { transform: scale(1.22); }
  60%  { transform: scale(1); }
}

.nuda-save-heartbeat:focus-visible {
  outline: 2px solid #e4ff54;
  outline-offset: 3px;
  border-radius: 8px;
}

@media (prefers-reduced-motion: reduce) {
  .nuda-save-heartbeat svg { transition: none; }
  .nuda-save-heartbeat.is-beat svg { animation: none; }
}
JavaScript
/* Save Heartbeat — vanilla JS
   Toggles saved state and triggers the heartbeat on save. */
(function () {
  document.querySelectorAll(".nuda-save-heartbeat").forEach(function (btn) {
    btn.addEventListener("click", function () {
      var pressed = btn.getAttribute("aria-pressed") === "true";
      btn.setAttribute("aria-pressed", String(!pressed));
      if (!pressed) {
        btn.classList.add("is-beat");
        setTimeout(function () { btn.classList.remove("is-beat"); }, 700);
      }
    });
  });
})();

How to use Save Heartbeat

Paste the HTML where you need it and the CSS into a global stylesheet (or a <style> tag). Every class is prefixed nuda- so it never collides with Tailwind or your own styles. Tweak the CSS custom properties to match your design system.

Works in React, Vue, Svelte, Astro, Next.js, Nuxt, Laravel Blade, Django, Rails — or a single .html file. No npm install, no build step.

More micro-interactions components

← Browse all NudaUI components