Components / Calender
Event Countdown Card
Free Calender React component — EventCountdownCard. MIT licensed, copy-paste ready for Next.js and Tailwind CSS.
Live countdown to an event — days, hours, and minutes update every minute. Editorial typography, no ticker gimmicks.
Preview
Countdown
Product launch
Tuesday, December 1
142
days
11
hrs
00
min
Setup
How to use
Free for personal and commercial use. No UI attribution required. Include the MIT copyright notice when copying component source into your project. Read the MIT license.
- 1
Run in your terminal:
$npm install clsx tailwind-merge lucide-react - 2
Copy
lib/cn.tsbelow. Skip if you already havecn(). - 3
Copy the code below and create
components/calender/event-countdown-card.tsxin your project. - 4
Import and render:
Exampleimport { EventCountdownCard } from "@/components/calender/event-countdown-card";
lib/cn.ts
import { type ClassValue, clsx } from "clsx";import { twMerge } from "tailwind-merge";export function cn(...inputs: ClassValue[]) {return twMerge(clsx(inputs));}
components/calender/event-countdown-card.tsx
"use client";import {forwardRef,useEffect,useMemo,useState,type ComponentPropsWithoutRef,} from "react";import { cn } from "@/lib/cn";function getCountdown(target: Date) {const diff = Math.max(0, target.getTime() - Date.now());const totalMinutes = Math.floor(diff / 60_000);const days = Math.floor(totalMinutes / (60 * 24));const hours = Math.floor((totalMinutes % (60 * 24)) / 60);const minutes = totalMinutes % 60;return { days, hours, minutes, done: diff <= 0 };}export type EventCountdownCardProps = Readonly<{title?: string;targetDate?: string;onComplete?: () => void;} & ComponentPropsWithoutRef<"div">>;// Event countdown — live days, hours, and minutes until a target date.export const EventCountdownCard = forwardRef<HTMLDivElement,EventCountdownCardProps>(({className,title = "Product launch",targetDate = "2026-12-01T09:00:00",onComplete,...props},ref,) => {const target = useMemo(() => new Date(targetDate), [targetDate]);const [countdown, setCountdown] = useState(() => getCountdown(target));useEffect(() => {const tick = () => {const next = getCountdown(target);setCountdown(next);if (next.done) onComplete?.();};tick();const timer = globalThis.setInterval(tick, 60_000);return () => globalThis.clearInterval(timer);}, [target, onComplete]);const dateLabel = target.toLocaleDateString("en-US", {weekday: "long",month: "long",day: "numeric",});return (<divref={ref}data-slot="event-countdown-card"className={cn("w-56 rounded-2xl border border-neutral-200/80 bg-white p-5 font-sans shadow-lg shadow-black/5 select-none",className,)}{...props}><p className="text-[11px] font-medium tracking-wide text-neutral-400 uppercase">Countdown</p><p className="mt-1 text-sm font-semibold text-neutral-900">{title}</p><p className="mt-0.5 text-xs text-neutral-500">{dateLabel}</p><div className="mt-5 grid grid-cols-3 gap-2 border-t border-neutral-100 pt-4">{[{ value: countdown.days, label: "days" },{ value: countdown.hours, label: "hrs" },{ value: countdown.minutes, label: "min" },].map((unit) => (<div key={unit.label} className="text-center"><pkey={`${unit.label}-${unit.value}`}className="text-2xl leading-none font-light text-neutral-900 tabular-nums opacity-100 starting:opacity-0 starting:translate-y-1 transition-all duration-500 ease-[cubic-bezier(0.25,0.46,0.45,0.94)]">{String(unit.value).padStart(2, "0")}</p><p className="mt-1 text-[10px] text-neutral-400 uppercase">{unit.label}</p></div>))}</div></div>);},);EventCountdownCard.displayName = "EventCountdownCard";