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.
// 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
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.
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.
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.
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.
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.
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.