HoverFree

Like Heart

A heart button that fires three simultaneous CSS animations on click: the heart pops with an elastic overshoot, a ring spreads out, and eight colored particles burst in a star pattern.

CSSAnimationInteractive

Updated

423 likes
Hover or click the scene to interact

This effect is free — the Hover category contains 48 effects total, including 5 free. Effect.Labs has 840 vanilla effects. Explore the category →

Usage examples

Orbiq — Internal social network
HR Department
Article of the week: how to manage your work-life balance in 2027.
31 likes
Innovation team
New internal tool deployed this month: project management module.
58 likes
IT department
Reminder: mandatory password rotation before Friday.
9 likes

① Positive reaction to a completed goal — habit-tracking dashboard for a life coach

WhenEach daily goal (meditation, exercise, reading) has a heart button; when the user marks the goal as done, all three animations burst together to celebrate the action. 56×56 px button centered below the goal label.
WhyAn expressive visual reward on goal completion strengthens the sense of achievement and motivates continued routine; a plain text highlight would lack that dopamine hit.
SettingsParticles in grass green (success colors rather than love red), counter replaced by a label activated by the .liked class.
FloraTag Garden Identify Favorites
Identified plant

Japanese Maple — Acer palmatum

Identified · 2 min ago · Confidence: 97%

Ornamental deciduous tree with palmate leaves, 5–9 lobes. Leaves turn bright red in autumn. Hardy in zones 5–8. Avoid waterlogging.

142 gardeners saved this

② Saving a favorite plant in a gardening app — mobile-web plant identification app for enthusiasts

WhenPlant ID card after photo recognition: the heart button saves it to My Favorites. 48×48 px button in the corner of the photo.
WhyA heart bursting with particles creates a satisfying moment on save, encouraging users to come back and browse their collection; a gardeners-who-saved count is displayed next to it.
SettingsLeaf-green and pollen-yellow particles, transparent button background, reduced to 48 px to fit in the card corner.
Today's goals — 2 / 3 completed Friday, 7 Nov
Meditation — 10 min
Reading — 20 min
Running — 30 min

③ Like button on an internal news article — communication platform of a public-services company

WhenHR department's article of the week; the heart button sits to the right of the title so employees can register their interest. Click target: full 56×56 px button.
WhyAn animated heart with a particle burst confirms the like expressively, more satisfying than a quiet +1 counter on a text-only layout.
SettingsParticles in corporate orange and blue, counter labelled with the reaction count to the right of the button.

How it works

The .fx-0361-root root (250 px tall, rgb(26, 26, 36) background) centers a column made up of the button and the counter. The .mi-heart-btn button is an 80 px circle. It holds the heart SVG, eight absolute span.mi-heart-particle elements (each positioned at 50% / 50% so they depart from the center) and an ::after pseudo-element for the ring. All animations are triggered by the .liked class being present on the button — no JavaScript beyond toggling that class.

Three CSS animations fire on click. @keyframes miHeartPop (0.4 s) scales the heart to 1.3× at 30%, pulls it back to 0.9× at 60%, then returns to 1 with a cubic-bezier(0.17, 0.89, 0.32, 1.49) that gives the bounce. @keyframes miHeartRing (0.6 s) expands the ::after from 0.8× to 1.4× as it fades out. @keyframes miParticleBurst (0.6 s) propels each span along an axis set by the --angle custom property (0°, 45°, 90°… 315°). The adjacent counter turns red as soon as .liked is present (CSS ~ .mi-heart-count sibling selector).

The script finds every .fx-0361-root on the page, adds a click listener to its button, and manages aria-pressed: “false” by default, “true” when .liked is present. Each copy of the component is isolated from the others.

Accessibility

  • Keyboard: the component is a real <button>; Tab reaches it, Space or Enter fires it. The focus outline (2 px indigo) only appears with keyboard navigation.
  • Announced state: aria-pressed is updated on every click. A screen reader announces the button as not pressed at rest and pressed once liked. Make sure the aria-label is translated into your page's language.
  • Reduced motion: under prefers-reduced-motion: reduce, the three animations are disabled; the color and fill transitions on the heart (grey to red) stay active. The liked state remains clearly readable without any motion.
  • Touch: the button responds normally to touch events. Its 80 px diameter target is comfortable for a finger (recommended minimum: 44 px).

Browser compatibility

Pure CSS (animations, --angle custom property, pseudo-elements) + 0.5 KB of JavaScript (classList, addEventListener, aria-pressed), no dependency. Verified in Chromium 151: three simultaneous animations, aria-pressed updated, two independent copies on the same page, 0 errors.

Chrome 49+✓ Full (measured)
Firefox 55+✓ Full
Safari 9.1+✓ Full
Edge 79+✓ Full (Chromium engine)
Mobile iOS 9.3+✓ Full
Android Chrome 49+✓ Full (Chromium engine)

Without JavaScript, animations still fire via the .liked class as long as an onclick attribute is present; aria-pressed will not be updated. Without CSS animations, the heart stays grey at rest and red on click (color change only).

The code

Copy the three blocks into your page. No dependencies.

HTML
<div class="fx-0361-root">
  <div class="mi-heart-scene">
    <button class="mi-heart-btn" aria-pressed="false" aria-label="Like">
      <svg viewBox="0 0 24 24"><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>
      <div class="mi-heart-particles">
        <span class="mi-heart-particle"></span>
        <span class="mi-heart-particle"></span>
        <span class="mi-heart-particle"></span>
        <span class="mi-heart-particle"></span>
        <span class="mi-heart-particle"></span>
        <span class="mi-heart-particle"></span>
        <span class="mi-heart-particle"></span>
        <span class="mi-heart-particle"></span>
      </div>
    </button>
    <span class="mi-heart-count">423 likes</span>
  </div>
</div>
CSS
.fx-0361-root,
.fx-0361-root *,
.fx-0361-root *::before,
.fx-0361-root *::after {
  box-sizing: border-box
}

.fx-0361-root {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 250px;
  overflow: hidden;
  position: relative;
  background: rgb(26, 26, 36);
  color: rgb(255, 255, 255);
  font-family: sans-serif;
}

.mi-heart-scene {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
}

.mi-heart-btn {
  width: 80px;
  height: 80px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.05);
  border: 2px solid rgba(255, 255, 255, 0.1);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  transition: background-color 0.3s ease, border-color 0.3s ease;
}

.mi-heart-btn:hover {
  background: rgba(239, 68, 68, 0.1);
  border-color: rgba(239, 68, 68, 0.3);
}

.mi-heart-btn:focus-visible {
  outline: 2px solid #a5b4fc;
  outline-offset: 3px;
}

.mi-heart-btn svg {
  width: 36px;
  height: 36px;
  stroke: rgba(255, 255, 255, 0.5);
  fill: none;
  stroke-width: 2;
  transition: stroke 0.3s ease, fill 0.3s ease;
}

.mi-heart-btn.liked svg {
  stroke: #ef4444;
  fill: #ef4444;
  animation: miHeartPop 0.4s cubic-bezier(0.17, 0.89, 0.32, 1.49);
}

.mi-heart-btn.liked::after {
  content: '';
  position: absolute;
  inset: -4px;
  border-radius: 50%;
  border: 2px solid #ef4444;
  animation: miHeartRing 0.6s ease-out forwards;
}

.mi-heart-particles {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.mi-heart-particle {
  position: absolute;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  top: 50%;
  left: 50%;
  opacity: 0;
}

.mi-heart-btn.liked .mi-heart-particle {
  animation: miParticleBurst 0.6s ease-out forwards;
}

.mi-heart-particle:nth-child(1) { background: #ef4444; --angle: 0deg; }
.mi-heart-particle:nth-child(2) { background: #f97316; --angle: 45deg; }
.mi-heart-particle:nth-child(3) { background: #eab308; --angle: 90deg; }
.mi-heart-particle:nth-child(4) { background: #ef4444; --angle: 135deg; }
.mi-heart-particle:nth-child(5) { background: #ec4899; --angle: 180deg; }
.mi-heart-particle:nth-child(6) { background: #f97316; --angle: 225deg; }
.mi-heart-particle:nth-child(7) { background: #eab308; --angle: 270deg; }
.mi-heart-particle:nth-child(8) { background: #ec4899; --angle: 315deg; }

.mi-heart-count {
  font-size: 0.85rem;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.5);
  transition: color 0.3s ease;
}

.mi-heart-btn.liked ~ .mi-heart-count {
  color: #ef4444;
}

@keyframes miHeartPop {
  0% { transform: scale(1); }
  30% { transform: scale(1.3); }
  60% { transform: scale(0.9); }
  100% { transform: scale(1); }
}

@keyframes miHeartRing {
  0% { transform: scale(0.8); opacity: 1; }
  100% { transform: scale(1.4); opacity: 0; }
}

@keyframes miParticleBurst {
  0% { opacity: 1; transform: translate(-50%, -50%) rotate(var(--angle)) translateY(0); }
  100% { opacity: 0; transform: translate(-50%, -50%) rotate(var(--angle)) translateY(-40px); }
}

@media (prefers-reduced-motion: reduce) {
  .mi-heart-btn { transition: background-color 0.15s, border-color 0.15s; }
  .mi-heart-btn svg { transition: stroke 0.15s, fill 0.15s; }
  .mi-heart-btn.liked svg { animation: none; }
  .mi-heart-btn.liked::after { animation: none; opacity: 0; }
  .mi-heart-btn.liked .mi-heart-particle { animation: none; }
}
JavaScript (fx-0361)
!function(){document.querySelectorAll('.fx-0361-root').forEach(function(root){var btn=root.querySelector('.mi-heart-btn');if(!btn)return;btn.addEventListener('click',function(){var liked=btn.classList.toggle('liked');btn.setAttribute('aria-pressed',liked?'true':'false');});})}();

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Heart color (CSS — stroke and fill on .mi-heart-btn.liked svg) #ef4444 (red) Switch to #ec4899 for fuchsia pink or #8b5cf6 for violet. Update the ring (::after border) and counter (.mi-heart-btn.liked ~ .mi-heart-count) to the same value.
Number of particles (HTML — number of span.mi-heart-particle elements) 8 particles, every 45° Reduce to 6 by removing 2 spans and setting --angle to 60°, 120°…, or raise to 12 at 30° intervals. Each new particle needs its --angle declared in CSS.
Particle travel distance (CSS — translateY(-40px) in miParticleBurst) 40 px 60 px gives a wider burst; 24 px a short burst that stays close to the button.
Button size (CSS — width / height of .mi-heart-btn) 80 px diameter Drop to 56 px for a dense list; raise to 100 px for a standalone use. Particle and SVG sizes follow if expressed in em.
Icon (HTML — SVG inside the button) Heart SVG Replace the SVG with a star, a flame or any other 24 px SVG icon. Adjust the miHeartPop rotation if the icon is asymmetric.
Counter (HTML — span.mi-heart-count) “423 likes” Update the number via JavaScript (count.textContent = ...) in your app logic. The color animation on the counter is pure CSS via the ~ sibling selector.

FAQ

Does the button go back to its initial state if clicked again?

Yes. The script uses classList.toggle('liked'): a second click removes the class, the animations do not replay, colors return to their default via CSS transition, and aria-pressed goes back to “false”.

Can several heart buttons coexist on the same page?

Yes, without restriction. The script finds every .fx-0361-root on the page and wires each button independently. Each copy has its own state: liking one does not affect the others.

Do the animations replay if you hover over the button after liking it?

No. The CSS animations are declared without animation-iteration-count: infinite: they play once when the class is added. A second hover without removing the class does not restart them.