Event Card
Original · freeAn event card with a date badge and details.
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-event.json"use client";
import { useState } from "react";
import { motion, AnimatePresence, useReducedMotion } from "motion/react";
type EventData = {
id: string;
month: string;
day: string;
weekday: string;
title: string;
time: string;
venue: string;
city: string;
category: string;
price: string;
image: string;
attendees: number;
featured?: boolean;
};
const EVENTS: EventData[] = [
{
id: "reactconf",
month: "OCT",
day: "14",
weekday: "Tuesday",
title: "React Conf 2026: The Compiler Era",
time: "9:00 AM – 6:00 PM PDT",
venue: "Moscone West",
city: "San Francisco, CA",
category: "Conference",
price: "$349",
image: "/img/gallery/g07.webp",
attendees: 1240,
featured: true,
},
{
id: "jazz",
month: "NOV",
day: "02",
weekday: "Sunday",
title: "Blue Note: Robert Glasper Trio",
time: "8:30 PM – 11:00 PM EST",
venue: "Blue Note Jazz Club",
city: "New York, NY",
category: "Live Music",
price: "$65",
image: "/img/gallery/g19.webp",
attendees: 210,
},
{
id: "workshop",
month: "SEP",
day: "27",
weekday: "Saturday",
title: "Hand-Thrown Ceramics Workshop",
time: "10:00 AM – 1:00 PM",
venue: "Clay & Kiln Studio",
city: "Portland, OR",
category: "Workshop",
price: "Free",
image: "/img/gallery/g31.webp",
attendees: 24,
},
];
function StatIcon({ path, className }: { path: string; className?: string }) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={1.8}
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
className={className}
>
<path d={path} />
</svg>
);
}
const ICON = {
clock: "M12 7v5l3 2M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z",
pin: "M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0ZM12 10a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z",
users:
"M17 20v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2M9 10a4 4 0 1 0 0-8 4 4 0 0 0 0 8ZM23 20v-2a4 4 0 0 0-3-3.87M16 2.13a4 4 0 0 1 0 7.75",
share:
"M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8M16 6l-4-4-4 4M12 2v13",
bookmark: "M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16Z",
check: "M20 6 9 17l-5-5",
ticket:
"M15 5v2M15 11v2M15 17v2M5 5h14a2 2 0 0 1 2 2v3a2 2 0 0 0 0 4v3a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-3a2 2 0 0 0 0-4V7a2 2 0 0 1 2-2Z",
calendar:
"M8 2v4M16 2v4M3 10h18M5 4h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2Z",
};
function DateBadge({
month,
day,
featured,
}: {
month: string;
day: string;
featured?: boolean;
}) {
return (
<div
className={[
"flex w-14 shrink-0 flex-col items-center overflow-hidden rounded-xl text-center shadow-sm ring-1",
featured
? "bg-indigo-600 text-white ring-indigo-500/40 dark:bg-indigo-500"
: "bg-white text-slate-900 ring-slate-900/10 dark:bg-slate-800 dark:text-slate-50 dark:ring-white/10",
].join(" ")}
>
<span
className={[
"w-full py-0.5 text-[10px] font-bold uppercase tracking-widest",
featured
? "bg-black/15 text-indigo-50"
: "bg-rose-500 text-white",
].join(" ")}
>
{month}
</span>
<span className="py-1 text-2xl font-black leading-none tabular-nums">
{day}
</span>
</div>
);
}
function AttendeeStack({ count }: { count: number }) {
const palette = ["bg-emerald-400", "bg-sky-400", "bg-amber-400", "bg-rose-400"];
return (
<div className="flex items-center gap-2">
<div className="flex -space-x-2">
{palette.map((c, i) => (
<span
key={i}
className={`h-6 w-6 rounded-full ring-2 ring-white dark:ring-slate-900 ${c}`}
/>
))}
</div>
<span className="text-xs font-medium text-slate-500 dark:text-slate-400">
{count.toLocaleString()} going
</span>
</div>
);
}
function EventCard({ event }: { event: EventData }) {
const reduce = useReducedMotion();
const [going, setGoing] = useState(false);
const [saved, setSaved] = useState(false);
const [copied, setCopied] = useState(false);
const [busy, setBusy] = useState(false);
const liveCount = event.attendees + (going ? 1 : 0);
async function handleShare() {
try {
await navigator.clipboard.writeText(
`${event.title} — ${event.month} ${event.day}, ${event.venue}`
);
setCopied(true);
window.setTimeout(() => setCopied(false), 1600);
} catch {
setCopied(false);
}
}
function handleRsvp() {
if (busy) return;
setBusy(true);
window.setTimeout(
() => {
setGoing((v) => !v);
setBusy(false);
},
reduce ? 0 : 620
);
}
return (
<article className="group relative flex flex-col overflow-hidden rounded-2xl bg-white ring-1 ring-slate-900/10 transition-shadow duration-300 hover:shadow-xl hover:shadow-slate-900/10 dark:bg-slate-900 dark:ring-white/10 dark:hover:shadow-black/40">
<div className="relative aspect-[16/10] w-full overflow-hidden">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={event.image}
alt={`${event.title} event cover`}
loading="lazy"
draggable={false}
className={[
"h-full w-full object-cover transition-transform duration-[900ms] ease-out",
reduce ? "" : "group-hover:scale-105",
].join(" ")}
/>
<div className="pointer-events-none absolute inset-0 bg-gradient-to-t from-black/55 via-black/5 to-transparent" />
<span className="absolute left-3 top-3 inline-flex items-center gap-1.5 rounded-full bg-black/45 px-2.5 py-1 text-[11px] font-semibold uppercase tracking-wide text-white backdrop-blur">
<StatIcon path={ICON.ticket} className="h-3.5 w-3.5" />
{event.category}
</span>
<button
type="button"
onClick={() => setSaved((v) => !v)}
aria-pressed={saved}
aria-label={saved ? "Remove from saved events" : "Save event"}
className="absolute right-3 top-3 inline-flex h-9 w-9 items-center justify-center rounded-full bg-white/90 text-slate-700 shadow-sm outline-none ring-1 ring-black/5 backdrop-blur transition-all hover:scale-105 focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 active:scale-95 dark:bg-slate-800/90 dark:text-slate-200"
>
<StatIcon
path={ICON.bookmark}
className={[
"h-[18px] w-[18px] transition-colors",
saved ? "fill-rose-500 text-rose-500" : "fill-transparent",
].join(" ")}
/>
</button>
<div className="absolute bottom-3 left-3 flex items-end gap-3">
<DateBadge
month={event.month}
day={event.day}
featured={event.featured}
/>
<div className="pb-0.5">
<p className="text-[11px] font-semibold uppercase tracking-wider text-white/80">
{event.weekday}
</p>
<p className="text-sm font-bold text-white drop-shadow">
{event.price === "Free" ? "Free entry" : `From ${event.price}`}
</p>
</div>
</div>
</div>
<div className="flex flex-1 flex-col gap-4 p-5">
<h3 className="text-lg font-bold leading-snug tracking-tight text-slate-900 dark:text-slate-50">
{event.title}
</h3>
<dl className="space-y-2 text-sm">
<div className="flex items-center gap-2.5 text-slate-600 dark:text-slate-300">
<StatIcon
path={ICON.clock}
className="h-4 w-4 shrink-0 text-indigo-500 dark:text-indigo-400"
/>
<dd>{event.time}</dd>
</div>
<div className="flex items-center gap-2.5 text-slate-600 dark:text-slate-300">
<StatIcon
path={ICON.pin}
className="h-4 w-4 shrink-0 text-indigo-500 dark:text-indigo-400"
/>
<dd>
<span className="font-medium text-slate-800 dark:text-slate-100">
{event.venue}
</span>
<span className="text-slate-400 dark:text-slate-500"> · {event.city}</span>
</dd>
</div>
</dl>
<div className="mt-auto flex items-center justify-between gap-3 border-t border-slate-100 pt-4 dark:border-white/10">
<AttendeeStack count={liveCount} />
<div className="flex items-center gap-2">
<div className="relative">
<button
type="button"
onClick={handleShare}
aria-label="Copy event details to clipboard"
className="inline-flex h-9 w-9 items-center justify-center rounded-lg text-slate-500 outline-none ring-1 ring-slate-900/10 transition-colors hover:bg-slate-50 hover:text-slate-800 focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 dark:text-slate-400 dark:ring-white/10 dark:hover:bg-white/5 dark:hover:text-slate-100 dark:focus-visible:ring-offset-slate-900"
>
<StatIcon
path={copied ? ICON.check : ICON.share}
className={[
"h-4 w-4",
copied ? "text-emerald-500" : "",
].join(" ")}
/>
</button>
<AnimatePresence>
{copied ? (
<motion.span
initial={reduce ? false : { opacity: 0, y: 4 }}
animate={{ opacity: 1, y: 0 }}
exit={reduce ? { opacity: 0 } : { opacity: 0, y: 4 }}
className="absolute -top-8 right-0 whitespace-nowrap rounded-md bg-slate-900 px-2 py-1 text-[11px] font-medium text-white shadow-lg dark:bg-slate-700"
>
Copied
</motion.span>
) : null}
</AnimatePresence>
</div>
<button
type="button"
onClick={handleRsvp}
aria-pressed={going}
disabled={busy}
className={[
"inline-flex min-w-[104px] items-center justify-center gap-1.5 rounded-lg px-4 py-2 text-sm font-semibold outline-none transition-all focus-visible:ring-2 focus-visible:ring-offset-2 active:scale-[0.97] disabled:cursor-wait dark:focus-visible:ring-offset-slate-900",
going
? "bg-emerald-500 text-white ring-1 ring-emerald-500 hover:bg-emerald-600 focus-visible:ring-emerald-500"
: "bg-indigo-600 text-white hover:bg-indigo-700 focus-visible:ring-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400",
].join(" ")}
>
{busy ? (
<svg
viewBox="0 0 24 24"
className="ce-spin h-4 w-4"
aria-hidden="true"
fill="none"
>
<circle
cx="12"
cy="12"
r="9"
stroke="currentColor"
strokeWidth="3"
className="opacity-25"
/>
<path
d="M21 12a9 9 0 0 0-9-9"
stroke="currentColor"
strokeWidth="3"
strokeLinecap="round"
/>
</svg>
) : going ? (
<>
<StatIcon path={ICON.check} className="h-4 w-4" />
Going
</>
) : (
"RSVP"
)}
</button>
</div>
</div>
</div>
</article>
);
}
export default function CardEvent() {
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 ce-spin { to { transform: rotate(360deg); } }
.ce-spin { animation: ce-spin 0.7s linear infinite; }
@media (prefers-reduced-motion: reduce) {
.ce-spin { animation-duration: 0.01ms; animation-iteration-count: 1; }
}
`}</style>
<div className="mx-auto max-w-6xl">
<header className="mb-10 flex flex-col gap-3 sm:mb-14">
<span className="inline-flex w-fit items-center gap-1.5 rounded-full bg-indigo-50 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-indigo-600 ring-1 ring-indigo-200 dark:bg-indigo-500/10 dark:text-indigo-300 dark:ring-indigo-400/20">
<StatIcon path={ICON.calendar} className="h-3.5 w-3.5" />
Upcoming
</span>
<h2 className="text-3xl font-black tracking-tight text-slate-900 sm:text-4xl dark:text-slate-50">
Events near you
</h2>
<p className="max-w-xl text-base text-slate-500 dark:text-slate-400">
RSVP, save for later, and share the ones worth showing up for. Tap a
card to lock in your spot.
</p>
</header>
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{EVENTS.map((event) => (
<EventCard key={event.id} event={event} />
))}
</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.

Profile Card
OriginalA user profile card with avatar, stats and a follow toggle.

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.

