Skip to content

Badge Ping Counter

An icon badge counter that bounces and pings once on every increment.

Freeplayfulminimal
Category
Components
Added
2026-08-24
Deps
2
Updated
2026-09-01
Preview

Props

3
Requires framer-motion, lucide-react
"use client";

import { useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { Bell } from "lucide-react";

export function BadgePingCounter({ initialCount = 0 }: { initialCount?: number }) {
  const [count, setCount] = useState(initialCount);

  return (
    <button
      onClick={() => setCount((c) => c + 1)}
      className="relative flex h-11 w-11 items-center justify-center rounded-full border border-neutral-200 text-neutral-500"
    >
      <Bell size={17} />
      {count > 0 && (
        <span className="absolute -right-1 -top-1 flex h-5 min-w-5 items-center justify-center rounded-full bg-red-500 px-1">
          <AnimatePresence mode="popLayout">
            <motion.span
              key={count}
              initial={{ scale: 0.4, opacity: 0 }}
              animate={{ scale: 1, opacity: 1 }}
              exit={{ scale: 0.4, opacity: 0 }}
              transition={{ type: "spring", stiffness: 500, damping: 22 }}
              className="text-[10px] font-bold text-white"
            >
              {count}
            </motion.span>
          </AnimatePresence>
          <motion.span
            key={`ring-${count}`}
            initial={{ scale: 1, opacity: 0.6 }}
            animate={{ scale: 2.2, opacity: 0 }}
            transition={{ duration: 0.5, ease: "easeOut" }}
            className="pointer-events-none absolute inset-0 rounded-full bg-red-500"
          />
        </span>
      )}
    </button>
  );
}
Notes

About this component

A bell icon with a badge number is easy; making an increment actually feel like something happened is the harder part. This component layers two independent Framer Motion animations on every count change: the number itself bounces in via AnimatePresence keyed on the count value, and a separate expanding ring, keyed as ring-${count} so it remounts fresh on every single increment, scales from 1 to 2.2 while fading to nothing behind it. That per-increment key is the part worth noticing; a fixed key on the ring would only ever animate once, on first mount, since React has no reason to remount an element whose key never changes. Reach for this over the plainer Animated Counter Badge when the ping itself is the point, a real-time notification feed where each new item should register as an event, not just a number tracking state. For anything incrementing faster than a person can read, like a live view counter, the ring just looks like flicker. It is a click-to-increment demo in the shipped code; a real integration replaces that with a websocket or polling update.

Curator’s note

The ping ring is keyed by `ring-${count}`, not a fixed key — a fixed key would only ever play the ring animation on the very first mount, since React has nothing to remount on subsequent increments without a key change.

Related

Pairs well with

Matched on shared tags and category — the entries most likely to be used alongside this one.

Pro

A large pulsing announcement pill anchors the hero above one headline.

herobadgeannouncement
Featured
NewPro

Toasts collapsed into a depth-scaled deck that fans out on hover.

toaststacknotification
NewPro

A round seal — the phrase fitted exactly around the circle with drawn dot separators — that turns once every 24 seconds, eases to a stop on hover, and is the link.

badgectasvg