Peer Stack

PeerStack is a high-density social proof component designed for modern dashboards. It visualizes active contributors or team members with orchestrated Framer Motion animations, magnetic status indicators, and context-aware tooltips. Engineered to stay sharp on any background with a built-in ring system and sub-pixel anti-aliasing.

Live Preview
Mritunjay
Sarah
Alex
+12

Dependencies

npm installframer-motion

Code

"use client";
import React, { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";

const data = [
  {
    name: "Mritunjay",
    role: "Lead Developer",
    img: "https://res.cloudinary.com/dmqwpwo6c/image/upload/v1776147234/myimg2_rfavkf.jpg",
    status: "Active",
    commits: 142,
    color: "text-emerald-500",
  },
  {
    name: "Sarah",
    role: "UI Engineer",
    img: "https://api.dicebear.com/7.x/avataaars/svg?seed=Liliana",
    status: "Active",
    commits: 89,
    color: "text-amber-500",
  },
  {
    name: "Alex",
    role: "Core Contributor",
    img: "https://api.dicebear.com/7.x/avataaars/svg?seed=Robert",
    status: "Idle",
    commits: 54,
    color: "text-zinc-400",
  },
];
export default function PeerStack({ USERS = data }) {
  const [hoveredUser, setHoveredUser] = useState<string | null>(null);

  return (
    <div className="flex items-center gap-4">
      <div className="flex -space-x-4">
        {USERS.map((u) => (
          <div
            key={u.name}
            className="relative"
            onMouseEnter={() => setHoveredUser(u.name)}
            onMouseLeave={() => setHoveredUser(null)}
          >
            {/* 1. TOOLTIP */}
            <AnimatePresence>
              {hoveredUser === u.name && (
                <motion.div
                  initial={{ opacity: 0, y: 10, scale: 0.9 }}
                  animate={{ opacity: 1, y: -45, scale: 1 }}
                  exit={{ opacity: 0, y: 10, scale: 0.9 }}
                  className={`absolute left-1/2  -translate-x-1/2 px-2 py-1 bg-zinc-900 dark:bg-white ${u.color}  text-[10px] font-mono rounded pointer-events-none whitespace-nowrap`}
                >
                  {u.name} <span className="opacity-50">[{u.role}]</span>
                  <div className="absolute -bottom-1 left-1/2 -translate-x-1/2 w-2 h-2 bg-inherit rotate-45" />
                </motion.div>
              )}
            </AnimatePresence>

            {/* 2. AVATAR */}
            <motion.div
              key={u.name}
              whileHover="hover" // This triggers the "hover" variant in all children
              className="relative"
            >
              <motion.img
                variants={{
                  hover: { scale: 1.1, zIndex: 50 },
                }}
                whileTap={{ scale: 0.9 }}
                src={u.img}
                className="w-14 h-14 relative rounded-full border-3 object-cover border-white dark:border-zinc-950 bg-zinc-100 dark:bg-zinc-800 cursor-default ring-1 ring-zinc-200 dark:ring-zinc-800"
                alt={u.name}
              />

              <motion.div
                variants={{
                  hover: { scale: 1.4, x: -2, y: -2 }, // Scale up and adjust position slightly on hover
                }}
                transition={{ type: "spring", stiffness: 300, damping: 20 }}
                className={`absolute w-2 h-2 ${
                  u.status === "Active"
                    ? "bg-green-500"
                    : u.status === "Idle"
                      ? "bg-amber-500"
                      : "bg-red-500"
                } top-0 left-2 rounded-full ring-1 ring-zinc-200 dark:ring-zinc-800 z-50 shadow-sm`}
              />
            </motion.div>
          </div>
        ))}

        {/* 3. THE "+X" INDICATOR */}
        <motion.div
          whileHover={{ y: -4 }}
          className="w-14 h-14 z-5 rounded-full bg-zinc-100 dark:bg-zinc-900 border-[3px] border-white dark:border-[#050505] flex items-center justify-center ring-1 ring-zinc-200 dark:ring-zinc-800"
        >
          <span className="text-[10px] font-black text-zinc-500 dark:text-zinc-400 font-mono">
            +12
          </span>
        </motion.div>
      </div>
    </div>
  );
}

Props

PropTypeDefaultDescription
USERSUserObject[]dataArray of user entities to be rendered in the stack.
namestring-Unique identifier and primary label for the tooltip.
rolestring-Professional designation displayed within the tooltip brackets.
imgstring-Source URL for the avatar image; rendered with object-cover.
status'Active' | 'Idle' | 'Offline'ActiveControls the background color of the animated status indicator.
commitsnumber0Optional metric for displaying activity depth or contribution weight.
colorstringtext-emerald-500Tailwind CSS class for the name highlight color in the tooltip.