Heart Burst Like
A like button that pops, fills and throws a small ring of sparks.
Preview and code
index.html
<button class="hb" type="button" aria-pressed="false"><span class="hb__h"><svg viewBox="0 0 24 24"><path d="M12 20s-7-4.4-7-10a4 4 0 0 1 7-2.6A4 4 0 0 1 19 10c0 5.6-7 10-7 10z"/></svg><span class="hb__ring"></span></span><span class="hb__n">128</span></button>
styles.css
.hb{display:inline-flex;align-items:center;gap:10px;padding:10px 16px;border:1px solid var(--line);border-radius:999px;background:transparent;color:var(--ink);font:600 16px/1 system-ui,sans-serif;cursor:pointer}
.hb:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
.hb__h{position:relative;width:24px;height:24px}
.hb svg{width:24px;height:24px;fill:none;stroke:currentColor;stroke-width:2;transition:transform .3s cubic-bezier(.3,1.6,.5,1)}
.hb[aria-pressed=true] svg{fill:#e11d48;stroke:#e11d48;transform:scale(1.15)}
.hb__ring{position:absolute;inset:-8px;border-radius:50%;background:radial-gradient(circle,#e11d48 2px,transparent 3px) 50% 0/6px 6px no-repeat,radial-gradient(circle,#f59e0b 2px,transparent 3px) 100% 50%/6px 6px no-repeat,radial-gradient(circle,#e11d48 2px,transparent 3px) 50% 100%/6px 6px no-repeat,radial-gradient(circle,#f59e0b 2px,transparent 3px) 0 50%/6px 6px no-repeat;opacity:0;transform:scale(.4)}
.hb.is-pop .hb__ring{animation:hb .5s ease-out}
@keyframes hb{0%{opacity:1;transform:scale(.4)}100%{opacity:0;transform:scale(1.4)}}
@media (prefers-reduced-motion:reduce){.hb.is-pop .hb__ring{animation:none}}
script.js
const b=document.querySelector('.hb');const n=b.querySelector('.hb__n');
b.addEventListener('click',()=>{const on=b.getAttribute('aria-pressed')!=='true';b.setAttribute('aria-pressed',String(on));n.textContent=Number(n.textContent)+(on?1:-1);
b.classList.remove('is-pop');void b.offsetWidth;if(on)b.classList.add('is-pop')});
heart-burst-like.tsx
"use client";
import { useState } from "react";
const css = `
.hb{display:inline-flex;align-items:center;gap:10px;padding:10px 16px;border:1px solid rgba(127,127,127,.3);border-radius:999px;background:transparent;color:inherit;font:600 16px/1 system-ui,sans-serif;cursor:pointer}
.hb:focus-visible{outline:2px solid #1659f5;outline-offset:3px}
.hb__h{position:relative;width:24px;height:24px}
.hb svg{width:24px;height:24px;fill:none;stroke:currentColor;stroke-width:2;transition:transform .3s cubic-bezier(.3,1.6,.5,1)}
.hb[aria-pressed=true] svg{fill:#e11d48;stroke:#e11d48;transform:scale(1.15)}
.hb__ring{position:absolute;inset:-8px;border-radius:50%;background:radial-gradient(circle,#e11d48 2px,transparent 3px) 50% 0/6px 6px no-repeat,radial-gradient(circle,#f59e0b 2px,transparent 3px) 100% 50%/6px 6px no-repeat,radial-gradient(circle,#e11d48 2px,transparent 3px) 50% 100%/6px 6px no-repeat,radial-gradient(circle,#f59e0b 2px,transparent 3px) 0 50%/6px 6px no-repeat;opacity:0;transform:scale(.4)}
.hb.is-pop .hb__ring{animation:hb .5s ease-out}
@keyframes hb{0%{opacity:1;transform:scale(.4)}100%{opacity:0;transform:scale(1.4)}}
@media (prefers-reduced-motion:reduce){.hb.is-pop .hb__ring{animation:none}}`;
export function HeartBurst({ count = 0, liked = false, onToggle }: { count?: number; liked?: boolean; onToggle?: (on: boolean) => void }) {
const [on, setOn] = useState(liked);
const [pop, setPop] = useState(0);
const toggle = () => { const next = !on; setOn(next); if (next) setPop((p) => p + 1); onToggle?.(next); };
return (
<>
<style>{css}</style>
<button type="button" aria-pressed={on} className={`hb${pop && on ? " is-pop" : ""}`} onClick={toggle} key={pop}>
<span className="hb__h" aria-hidden="true">
<svg viewBox="0 0 24 24"><path d="M12 20s-7-4.4-7-10a4 4 0 0 1 7-2.6A4 4 0 0 1 19 10c0 5.6-7 10-7 10z" /></svg>
<span className="hb__ring" />
</span>
<span>{count + (on && !liked ? 1 : 0) - (!on && liked ? 1 : 0)}</span>
</button>
</>
);
}
export default HeartBurst;
Install
npx shadcn@latest add https://ownlyplans.com/r/bhaskar/heart-burst-like.json
Adds the React files to components/ownlyplans/ in a project set up with shadcn and Tailwind CSS. Or copy them from the Code tab.
About
Sparks are six pseudo-dots on a ring that scales out once. The count updates and aria-pressed reflects the state.