Profile Card
Original · freeA user profile card with avatar, stats and a follow toggle.
Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.
npx shadcn@latest add https://webinnoventix.com/r/card-profile.json"use client";
import { useState } from "react";
import { useReducedMotion } from "motion/react";
type Person = {
id: string;
name: string;
handle: string;
role: string;
bio: string;
avatar: string;
cover: string;
verified: boolean;
followsYou: boolean;
location: string;
stats: { posts: number; followers: number; following: number };
accent: "indigo" | "emerald" | "rose";
};
const PEOPLE: Person[] = [
{
id: "amelia",
name: "Amelia Hart",
handle: "ameliabuilds",
role: "Staff Design Engineer",
bio: "Prototyping design systems at Vercel. I write about motion, tokens, and the boring parts of good UI.",
avatar: "/img/gallery/g14.webp",
cover: "/img/gallery/g03.webp",
verified: true,
followsYou: true,
location: "Lisbon, PT",
stats: { posts: 486, followers: 28400, following: 512 },
accent: "indigo",
},
{
id: "marcus",
name: "Marcus Ilori",
handle: "marcusml",
role: "ML Research · Latent Labs",
bio: "Training small models that punch above their weight. Ex-DeepMind. Currently obsessed with retrieval.",
avatar: "/img/gallery/g21.webp",
cover: "/img/gallery/g09.webp",
verified: true,
followsYou: false,
location: "Berlin, DE",
stats: { posts: 1203, followers: 91200, following: 340 },
accent: "emerald",
},
{
id: "priya",
name: "Priya Nair",
handle: "priyaships",
role: "Founder · Cadence Analytics",
bio: "Building the reporting layer for indie SaaS. Bootstrapped to $40k MRR. Sharing the messy middle.",
avatar: "/img/gallery/g27.webp",
cover: "/img/gallery/g18.webp",
verified: false,
followsYou: true,
location: "Bengaluru, IN",
stats: { posts: 742, followers: 15800, following: 889 },
accent: "rose",
},
];
const ACCENTS: Record<
Person["accent"],
{
ring: string;
text: string;
grad: string;
btn: string;
btnFollowing: string;
softBg: string;
}
> = {
indigo: {
ring: "ring-indigo-500/40 dark:ring-indigo-400/40",
text: "text-indigo-600 dark:text-indigo-400",
grad: "from-indigo-500/25 via-violet-500/10 to-transparent",
btn: "bg-indigo-600 hover:bg-indigo-500 text-white focus-visible:outline-indigo-600 dark:focus-visible:outline-indigo-400",
btnFollowing:
"bg-white text-slate-700 ring-1 ring-inset ring-slate-300 hover:ring-rose-300 hover:text-rose-600 dark:bg-slate-800 dark:text-slate-200 dark:ring-slate-600 dark:hover:text-rose-400 focus-visible:outline-slate-400",
softBg: "bg-indigo-50 dark:bg-indigo-500/10",
},
emerald: {
ring: "ring-emerald-500/40 dark:ring-emerald-400/40",
text: "text-emerald-600 dark:text-emerald-400",
grad: "from-emerald-500/25 via-sky-500/10 to-transparent",
btn: "bg-emerald-600 hover:bg-emerald-500 text-white focus-visible:outline-emerald-600 dark:focus-visible:outline-emerald-400",
btnFollowing:
"bg-white text-slate-700 ring-1 ring-inset ring-slate-300 hover:ring-rose-300 hover:text-rose-600 dark:bg-slate-800 dark:text-slate-200 dark:ring-slate-600 dark:hover:text-rose-400 focus-visible:outline-slate-400",
softBg: "bg-emerald-50 dark:bg-emerald-500/10",
},
rose: {
ring: "ring-rose-500/40 dark:ring-rose-400/40",
text: "text-rose-600 dark:text-rose-400",
grad: "from-rose-500/25 via-amber-500/10 to-transparent",
btn: "bg-rose-600 hover:bg-rose-500 text-white focus-visible:outline-rose-600 dark:focus-visible:outline-rose-400",
btnFollowing:
"bg-white text-slate-700 ring-1 ring-inset ring-slate-300 hover:ring-rose-300 hover:text-rose-600 dark:bg-slate-800 dark:text-slate-200 dark:ring-slate-600 dark:hover:text-rose-400 focus-visible:outline-slate-400",
softBg: "bg-rose-50 dark:bg-rose-500/10",
},
};
function formatCount(n: number): string {
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1).replace(/\.0$/, "")}M`;
if (n >= 1_000) return `${(n / 1_000).toFixed(1).replace(/\.0$/, "")}K`;
return `${n}`;
}
function VerifiedBadge({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 24 24"
aria-hidden="true"
className={className}
fill="currentColor"
>
<path d="M12 2.2l2.14 1.56 2.64-.2.9 2.5 2.29 1.34-.63 2.58 1.06 2.42-1.86 1.9.2 2.65-2.55.72-1.45 2.22-2.6-.55L12 21.8l-2.14-1.86-2.6.55-1.45-2.22-2.55-.72.2-2.65-1.86-1.9L2.66 9.4l-.63-2.58L4.32 5.5l.9-2.5 2.64.2L12 2.2z" />
<path
d="M8.6 12.2l2.2 2.2 4.4-4.6"
fill="none"
stroke="#fff"
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
function ProfileCard({ person }: { person: Person }) {
const [following, setFollowing] = useState(false);
const [hovering, setHovering] = useState(false);
const [pulseKey, setPulseKey] = useState(0);
const reduce = useReducedMotion();
const a = ACCENTS[person.accent];
const liveFollowers =
person.stats.followers + (following ? 1 : 0);
const toggle = () => {
setFollowing((v) => !v);
setPulseKey((k) => k + 1);
};
const btnLabel = following
? hovering
? "Unfollow"
: "Following"
: "Follow";
return (
<article className="group relative flex w-full max-w-sm flex-col overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm ring-1 ring-slate-900/5 transition-shadow duration-300 hover:shadow-xl dark:border-slate-800 dark:bg-slate-900 dark:ring-white/10">
{/* Cover */}
<div className="relative h-24 w-full overflow-hidden">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={person.cover}
alt=""
loading="lazy"
draggable={false}
className="h-full w-full object-cover"
/>
<div
className={`absolute inset-0 bg-gradient-to-br ${a.grad} mix-blend-multiply dark:mix-blend-screen`}
/>
<div className="absolute inset-0 bg-gradient-to-t from-white/60 to-transparent dark:from-slate-900/70" />
</div>
{/* Avatar */}
<div className="relative -mt-10 px-5">
<div className="flex items-end justify-between">
<div
className={`cpx-float relative h-20 w-20 shrink-0 overflow-hidden rounded-full bg-white ring-4 ring-white dark:bg-slate-900 dark:ring-slate-900`}
>
<span
className={`absolute inset-0 rounded-full ring-2 ring-inset ${a.ring}`}
aria-hidden="true"
/>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={person.avatar}
alt={`${person.name}'s avatar`}
loading="lazy"
draggable={false}
className="h-full w-full object-cover"
/>
<span
className="absolute bottom-1 right-1 h-3.5 w-3.5 rounded-full border-2 border-white bg-emerald-500 dark:border-slate-900"
aria-label="Online"
role="img"
/>
</div>
<button
type="button"
onClick={toggle}
onMouseEnter={() => setHovering(true)}
onMouseLeave={() => setHovering(false)}
onBlur={() => setHovering(false)}
aria-pressed={following}
aria-label={
following
? `Following ${person.name}. Activate to unfollow.`
: `Follow ${person.name}`
}
className={`relative mb-1 inline-flex h-9 items-center gap-1.5 overflow-hidden rounded-full px-4 text-sm font-semibold transition-all duration-200 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 active:scale-95 ${
following ? a.btnFollowing : a.btn
}`}
>
{!reduce && pulseKey > 0 && (
<span
key={pulseKey}
aria-hidden="true"
className="cpx-ripple pointer-events-none absolute inset-0 rounded-full"
/>
)}
<svg
viewBox="0 0 24 24"
aria-hidden="true"
className="h-4 w-4"
fill="none"
stroke="currentColor"
strokeWidth="2.2"
strokeLinecap="round"
strokeLinejoin="round"
>
{following ? (
<path d="M20 6L9 17l-5-5" />
) : (
<>
<path d="M12 5v14M5 12h14" />
</>
)}
</svg>
<span className="relative">{btnLabel}</span>
</button>
</div>
</div>
{/* Identity */}
<div className="px-5 pt-3">
<div className="flex items-center gap-1.5">
<h3 className="truncate text-base font-semibold text-slate-900 dark:text-white">
{person.name}
</h3>
{person.verified && (
<VerifiedBadge
className={`h-4 w-4 shrink-0 ${a.text}`}
/>
)}
</div>
<div className="mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-1 text-sm">
<span className="text-slate-500 dark:text-slate-400">
@{person.handle}
</span>
{person.followsYou && (
<span className="rounded bg-slate-100 px-1.5 py-0.5 text-[11px] font-medium text-slate-500 dark:bg-slate-800 dark:text-slate-400">
Follows you
</span>
)}
</div>
<p className={`mt-1.5 text-xs font-medium ${a.text}`}>
{person.role}
</p>
<p className="mt-2 text-sm leading-relaxed text-slate-600 dark:text-slate-300">
{person.bio}
</p>
<p className="mt-3 inline-flex items-center gap-1.5 text-xs text-slate-500 dark:text-slate-400">
<svg
viewBox="0 0 24 24"
aria-hidden="true"
className="h-3.5 w-3.5"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M12 21s-7-5.2-7-11a7 7 0 0114 0c0 5.8-7 11-7 11z" />
<circle cx="12" cy="10" r="2.5" />
</svg>
{person.location}
</p>
</div>
{/* Stats */}
<div className="mt-4 grid grid-cols-3 divide-x divide-slate-200 border-t border-slate-200 dark:divide-slate-800 dark:border-slate-800">
<Stat label="Posts" value={formatCount(person.stats.posts)} />
<Stat
label="Followers"
value={formatCount(liveFollowers)}
live={following}
accent={a}
/>
<Stat
label="Following"
value={formatCount(person.stats.following)}
/>
</div>
</article>
);
}
function Stat({
label,
value,
live,
accent,
}: {
label: string;
value: string;
live?: boolean;
accent?: (typeof ACCENTS)[Person["accent"]];
}) {
return (
<div className="flex flex-col items-center py-3">
<span
className={`text-sm font-semibold tabular-nums transition-colors ${
live && accent
? accent.text
: "text-slate-900 dark:text-white"
}`}
>
{value}
</span>
<span className="mt-0.5 text-[11px] font-medium uppercase tracking-wide text-slate-400 dark:text-slate-500">
{label}
</span>
</div>
);
}
export default function CardProfile() {
return (
<section className="relative w-full bg-slate-50 px-4 py-16 sm:px-6 sm:py-20 dark:bg-slate-950">
<style>{`
@keyframes cpx-float-kf {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-4px); }
}
@keyframes cpx-ripple-kf {
0% { transform: scale(0.2); opacity: 0.5; }
100% { transform: scale(2.4); opacity: 0; }
}
.cpx-float { animation: cpx-float-kf 5s ease-in-out infinite; }
.cpx-ripple {
background: radial-gradient(circle, currentColor 0%, transparent 60%);
animation: cpx-ripple-kf 0.6s ease-out forwards;
}
@media (prefers-reduced-motion: reduce) {
.cpx-float { animation: none; }
.cpx-ripple { animation: none; display: none; }
}
`}</style>
<div className="mx-auto max-w-6xl">
<header className="mx-auto mb-12 max-w-2xl text-center">
<span className="inline-flex items-center rounded-full bg-indigo-50 px-3 py-1 text-xs font-semibold text-indigo-600 ring-1 ring-inset ring-indigo-500/20 dark:bg-indigo-500/10 dark:text-indigo-400">
Profile Cards
</span>
<h2 className="mt-4 text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl dark:text-white">
Follow the makers
</h2>
<p className="mt-3 text-base text-slate-600 dark:text-slate-400">
Tap follow and the count updates live. Accessible toggles, real
focus rings, and motion that respects your settings.
</p>
</header>
<div className="grid grid-cols-1 justify-items-center gap-8 sm:grid-cols-2 lg:grid-cols-3">
{PEOPLE.map((p) => (
<ProfileCard key={p.id} person={p} />
))}
</div>
</div>
</section>
);
}Dependencies
Licence
Built by Web Innoventix. Free for personal and commercial use, no attribution required.
Built by Web Innoventix
Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.
Get a free quoteSimilar components
Browse all →
3D Tilt Cards
OriginalA responsive grid of cards that tilt in 3D toward your cursor with a light glare and lifted depth layers, powered by Framer Motion springs.

Spotlight Follow Cards
OriginalFeature cards where a soft radial spotlight and a glowing border chase the cursor on hover, with a staggered scroll reveal and an animated shimmer line.

3D Flip Cards
OriginalPricing-style cards that flip in 3D on hover or tap to reveal details on the back, keyboard accessible with a staggered entrance animation.

Animated Gradient Border Cards
OriginalCards wrapped in a live conic gradient border that rotates and speeds up on hover, with a shimmer sweep and a scrolling tag marquee.

Glow Effect Card
primitivesA reusable glow effect card for React and Tailwind, with hover and motion.

Neon Gradient Card
gradient-card.tsxA reusable neon gradient card for React and Tailwind, with hover and motion.

Spotlight Magic Card
card.tsxA reusable spotlight magic card for React and Tailwind, with hover and motion.

Tilt Spotlight Card
primitivesA reusable tilt spotlight card for React and Tailwind, with hover and motion.

Product Card
OriginalA product card with image, price and add-to-cart feedback.

Pricing Single Card
OriginalA single pricing plan card with feature list and CTA.

Stat Card
OriginalA metric card with value, trend arrow and a small sparkline.

Blog Card
OriginalA blog post card with cover image, tag and meta row.

