Concentric Ripple Rings Background with Floating Avatars

A light section with a set of concentric rings behind a central icon. The rings breathe in and out one after another, so a wave appears to travel outward like a radar sweep or a location search. Four name chips float on the rings around the icon, and a headline, a sentence and two buttons sit underneath. On phones the rings stay, the chips are hidden and the text stacks below.

Code

// Adapted from Magic UI (https://magicui.design/docs/components/ripple)
// Copyright (c) Magic UI. MIT License: https://we.inc/components/licenses
import { motion, useReducedMotion } from "framer-motion";

/** Ring settings: size of the first ring, how much each next one grows, and how many there are. */
const MAIN_SIZE = 190;
const STEP = 84;
const RINGS = 8;
const MAIN_OPACITY = 0.3;

/** People placed around the rings. `x` and `y` are offsets from the centre in pixels. */
const people = [
  { initials: "AK", name: "Amara K.", note: "2 streets away", x: -250, y: -70, color: "bg-rose-500" },
  { initials: "TB", name: "Tomas B.", note: "Walks at 7 am", x: 235, y: -112, color: "bg-amber-500" },
  { initials: "LN", name: "Linh N.", note: "Has a whippet", x: -168, y: 124, color: "bg-violet-500" },
  { initials: "RO", name: "Rafa O.", note: "New this week", x: 205, y: 104, color: "bg-sky-500" },
];

function Ripple() {
  return (
    <div aria-hidden="true" className="pointer-events-none absolute inset-0 select-none" style={{ maskImage: "radial-gradient(closest-side, #000 35%, transparent 100%)", WebkitMaskImage: "radial-gradient(closest-side, #000 35%, transparent 100%)" }}>
      {Array.from({ length: RINGS }, (_, i) => {
        const size = MAIN_SIZE + i * STEP;
        return (
          <span
            key={i}
            className="concentric-ripple-rings-background-ring absolute left-1/2 top-1/2 rounded-full border border-teal-700/40 bg-teal-600/20 shadow-xl shadow-teal-900/10"
            style={{ width: size, height: size, opacity: Math.max(MAIN_OPACITY - i * 0.034, 0.03), animationDelay: `${i * 0.09}s` }}
          />
        );
      })}
    </div>
  );
}

export default function ConcentricRippleRingsBackgroundWithFloatingAvatars() {
  const reduce = useReducedMotion();

  return (
    <section className="overflow-hidden bg-[#f3f8f6] px-4 py-16 sm:px-6 sm:py-20">
      <style>{`
        @keyframes concentric-ripple-rings-background-pulse {
          0%, 100% { transform: translate(-50%, -50%) scale(1); }
          50% { transform: translate(-50%, -50%) scale(0.9); }
        }
        .concentric-ripple-rings-background-ring {
          transform: translate(-50%, -50%);
          animation: concentric-ripple-rings-background-pulse 2.4s ease-in-out infinite;
        }
        @media (prefers-reduced-motion: reduce) {
          .concentric-ripple-rings-background-ring { animation: none; }
        }
      `}</style>

      <div className="mx-auto max-w-5xl text-center">
        <div className="relative mx-auto h-[380px] w-full max-w-3xl sm:h-[440px]">
          <Ripple />

          {/* The thing the rings are drawing attention to. */}
          <div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">
            <motion.div
              initial={reduce ? false : { scale: 0.6, opacity: 0 }}
              animate={{ scale: 1, opacity: 1 }}
              transition={{ type: "spring", stiffness: 180, damping: 14 }}
              className="flex h-24 w-24 items-center justify-center rounded-[28px] bg-teal-700 text-white shadow-2xl shadow-teal-900/40 ring-8 ring-white/70"
            >
              <svg viewBox="0 0 24 24" className="h-11 w-11" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                <path d="M12 21s-7-5.6-7-11a7 7 0 0114 0c0 5.400-7 11-7 11z" />
                <circle cx="12" cy="10" r="2.6" />
              </svg>
            </motion.div>
          </div>

          <ul className="absolute left-1/2 top-1/2 hidden sm:block">
            {people.map((p, i) => (
              <motion.li
                key={p.initials}
                className="absolute"
                style={{ left: p.x, top: p.y }}
                initial={reduce ? false : { opacity: 0, scale: 0.5 }}
                animate={reduce ? { opacity: 1, scale: 1 } : { opacity: 1, scale: 1, y: [0, -7, 0] }}
                transition={{
                  opacity: { delay: 0.25 + i * 0.12, duration: 0.4 },
                  scale: { delay: 0.25 + i * 0.12, type: "spring", stiffness: 220, damping: 16 },
                  y: { duration: 4 + i * 0.6, repeat: Infinity, ease: "easeInOut", delay: i * 0.4 },
                }}
              >
                <div className="flex -translate-x-1/2 -translate-y-1/2 items-center gap-2.5 whitespace-nowrap rounded-full bg-white py-1.5 pl-1.5 pr-4 text-left shadow-lg shadow-teal-900/10 ring-1 ring-teal-900/5">
                  <span className={`flex h-9 w-9 items-center justify-center rounded-full text-xs font-semibold text-white ${p.color}`}>{p.initials}</span>
                  <span>
                    <span className="block text-sm font-semibold leading-tight text-slate-900">{p.name}</span>
                    <span className="block text-xs leading-tight text-slate-600">{p.note}</span>
                  </span>
                </div>
              </motion.li>
            ))}
          </ul>
        </div>

        <div className="relative -mt-6 sm:-mt-10">
          <p className="text-sm font-semibold uppercase tracking-widest text-teal-700">Looking near Alder Park</p>
          <h2 className="mx-auto mt-3 max-w-2xl text-4xl font-semibold tracking-tight text-slate-900 sm:text-5xl">Find a walking partner around the corner</h2>
          <p className="mx-auto mt-4 max-w-lg text-lg leading-relaxed text-slate-600">Pawpath matches dog owners who live close by and keep the same hours. 64 neighbours are out within ten minutes of you.</p>
          <div className="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
            <a href="#" className="inline-flex h-12 items-center rounded-full bg-teal-700 px-7 text-sm font-semibold text-white transition-colors hover:bg-teal-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-teal-700 focus-visible:ring-offset-2 focus-visible:ring-offset-[#f3f8f6]">
              Share my location
            </a>
            <a href="#" className="inline-flex h-12 items-center rounded-full px-6 text-sm font-semibold text-teal-800 transition-colors hover:bg-teal-700/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-teal-700">
              Enter a postcode
            </a>
          </div>
        </div>
      </div>
    </section>
  );
}

Install with one command: npx shadcn@latest add https://we.inc/r/concentric-ripple-rings-background-with-floating-avatars.json

When to use it

How to customize it

Accessibility

Questions

How does the ripple effect work?

Eight circles of growing size are centred on the same point. Every circle runs the same small scale animation, but each one starts a fraction of a second after the one inside it. That offset is what the eye reads as a wave moving outward.

Why do the rings fade toward the edges?

Each ring is drawn a little more transparent than the last, and the whole layer has a radial mask that fades to nothing at the edge of its box. This stops the largest rings from ending in a hard line where the container is clipped.

Can I put the rings behind a button or a photo?

Yes. `Ripple` fills whatever `relative` box it is placed in. Put it as the first child and position your own content in the centre over it.

Is the animation expensive?

No. It is a CSS transform on eight elements with no script. The soft shadow on each ring is the only costly part, and you can remove `shadow-xl` if you need to.

How do I make the rings expand and vanish instead of breathing?

Change the keyframes to go from `scale(0.6)` with full opacity to `scale(1.3)` with zero opacity, and remove the `50%` step. Keep the per-ring delay so they leave one after another.

Adapted from Ripple in Magic UI, used under its open-source licence. Licences and credits

Related: Dot Matrix Background with Ripples and Pointer Spotlight, Hero with Orbiting App Icons Around a Logo, Particle Network Background with Connecting Lines.

More animated backgrounds or all free UI components. Start for free to have a full site built around it.

More in Backgrounds

We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.

Product

Who It's For

Features

Resources

Company

View Sitemap