Like Burst

Tapping the heart squashes it, refills it in colour and throws a ring of small sparks outward.

  • CSS
  • Buttons
  • Click
  • Added

AI prompt

Create a React like button with a CSS burst. The button holds a heart SVG and toggles aria-pressed. When it turns on: scale the heart 1 → 0.8 → 1.15 → 1 over 450ms with cubic-bezier(0.16, 1, 0.3, 1) and switch its fill from transparent to the accent; at the same time play a ring of 8 small dots positioned around the heart (rotate(calc(var(--i) * 45deg)) translateY(-10px)) that move out to translateY(-30px) and fade from opacity 1 to 0 over 500ms. Restart the burst on every like by changing a key. Turning it off just empties the heart, with no burst. Show the count next to it with tabular-nums. Use aria-label "Like" and update it to "Liked". Under prefers-reduced-motion, skip the scale and the sparks and only change the fill.

Source

Demo.tsx
'use client'

import { useState } from 'react'
import s from './demo.module.css'

export default function LikeBurst({ labels }: { labels: Record<string, string> }) {
  const [liked, setLiked] = useState(false)
  const [burst, setBurst] = useState(0)

  const toggle = () => {
    if (!liked) setBurst(burst + 1)
    setLiked(!liked)
  }

  return (
    <button type="button" className={s.button} aria-pressed={liked} aria-label={liked ? labels.liked : labels.like} onClick={toggle}>
      <span className={s.icon}>
        <svg key={burst} className={s.heart} viewBox="0 0 24 24" aria-hidden="true">
          <path d="M12 20.5s-7.5-4.6-7.5-10.2A4.3 4.3 0 0 1 12 7.6a4.3 4.3 0 0 1 7.5 2.7c0 5.6-7.5 10.2-7.5 10.2Z" />
        </svg>
        {liked && (
          <span key={`b${burst}`} className={s.burst} aria-hidden="true">
            {Array.from({ length: 8 }, (_, i) => (
              <i key={i} style={{ '--i': i } as React.CSSProperties} />
            ))}
          </span>
        )}
      </span>
      <span className={s.count}>{liked ? 129 : 128}</span>
    </button>
  )
}
demo.module.css
.button {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  height: 52px;
  padding: 0 20px 0 16px;
  border: 1px solid var(--stage-rule);
  border-radius: 999px;
  background: var(--stage-surface);
  color: var(--stage-ink);
  font: 600 var(--text-base) / 1 var(--font-body);
  cursor: pointer;
}

.button:hover { border-color: var(--stage-muted); }
.button:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 3px; }

.icon { position: relative; display: grid; place-items: center; width: 26px; height: 26px; }

.heart { width: 26px; height: 26px; overflow: visible; }

.heart path {
  fill: transparent;
  stroke: var(--stage-ink);
  stroke-width: 1.8;
  transition: fill 150ms linear, stroke 150ms linear;
}

.button[aria-pressed='true'] .heart { animation: pop 450ms var(--ease-out); }
.button[aria-pressed='true'] .heart path { fill: var(--stage-accent); stroke: var(--stage-accent); }

@keyframes pop {
  0% { transform: scale(1); }
  25% { transform: scale(0.8); }
  60% { transform: scale(1.15); }
  100% { transform: scale(1); }
}

.burst { position: absolute; inset: 0; pointer-events: none; }

.burst i {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 5px;
  height: 5px;
  margin: -2.5px;
  border-radius: 50%;
  background: var(--stage-accent);
  transform: rotate(calc(var(--i) * 45deg)) translateY(-10px);
  animation: spark 500ms var(--ease-out) forwards;
}

.burst i:nth-child(even) { background: var(--stage-ink); }

@keyframes spark {
  to { transform: rotate(calc(var(--i) * 45deg)) translateY(-30px); opacity: 0; }
}

.count { font-variant-numeric: tabular-nums; }

@media (prefers-reduced-motion: reduce) {
  .button[aria-pressed='true'] .heart { animation: none; }
  .burst { display: none; }
}

Suggest an idea

Seen a motion or a theme you would like to find here? Describe it, and it opens as a prefilled GitHub issue for you to post.

What are you suggesting?

Needs a GitHub account. Suggestions are public.