Componentmedia0.97 KB gzipZero dependencies

Countdown

Live ticking event timer with segmented cards.

Preview

Live Preview
00DAYS
:
00HOURS
:
00MINS
:
00SECS

Installation

Tokens guide →

Add to your project via CLI (zero dependencies, code you own):

npx bigbullui add countdown

Or install the full npm package:

npm install bigbullui
import { Countdown } from "@/components/ui/countdown";

<Countdown targetDate={new Date("2026-12-31T23:59:59")} />

Usage guidance

Use Countdown when a ticket stub surface needs Live ticking event timer with segmented cards. Keep props minimal and prefer composition with sibling stubs.

Avoid Countdown for long form flows or dense data grids where a dedicated layout block fits better.

Source code

countdown.tsx

Zero dependencies (only React + utils). Copy and paste directly into your project:

"use client";

import * as React from "react";
import { cn } from "./lib/utils";

export interface CountdownProps extends React.HTMLAttributes<HTMLDivElement> {
  targetDate: Date | string;
  onComplete?: () => void;
  className?: string;
}

export function Countdown({
  targetDate,
  onComplete,
  className,
  ...props
}: CountdownProps) {
  const calculateTimeLeft = React.useCallback(() => {
    const difference = +new Date(targetDate) - +new Date();
    if (difference <= 0) {
      return { days: 0, hours: 0, minutes: 0, seconds: 0, completed: true };
    }
    return {
      days: Math.floor(difference / (1000 * 60 * 60 * 24)),
      hours: Math.floor((difference / (1000 * 60 * 60)) % 24),
      minutes: Math.floor((difference / 1000 / 60) % 60),
      seconds: Math.floor((difference / 1000) % 60),
      completed: false,
    };
  }, [targetDate]);

  const [timeLeft, setTimeLeft] = React.useState({
    days: 0,
    hours: 0,
    minutes: 0,
    seconds: 0,
    completed: false,
  });

  React.useEffect(() => {
    setTimeLeft(calculateTimeLeft());
    const timer = setInterval(() => {
      const next = calculateTimeLeft();
      setTimeLeft(next);
      if (next.completed) {
        clearInterval(timer);
        onComplete?.();
      }
    }, 1000);
    return () => clearInterval(timer);
  }, [calculateTimeLeft, onComplete]);

  const units = [
    { label: "DAYS", value: timeLeft.days },
    { label: "HOURS", value: timeLeft.hours },
    { label: "MINS", value: timeLeft.minutes },
    { label: "SECS", value: timeLeft.seconds },
  ];

  return (
    <div
      role="timer"
      aria-label="Countdown timer"
      className={cn("inline-flex items-center gap-2 select-none", className)}
      {...props}
    >
      {units.map((unit, idx) => (
        <React.Fragment key={unit.label}>
          <div className="flex flex-col items-center justify-center rounded-md border-2 border-foreground bg-card px-3 py-2 text-center shadow-xs min-w-[56px]">
            <span className="font-mono text-xl font-black text-foreground">
              {String(unit.value).padStart(2, "0")}
            </span>
            <span className="font-mono text-[9px] uppercase tracking-wider text-muted-foreground mt-0.5">
              {unit.label}
            </span>
          </div>
          {idx < units.length - 1 ? (
            <span className="font-mono text-lg font-bold text-muted-foreground">:</span>
          ) : null}
        </React.Fragment>
      ))}
    </div>
  );
}

Props

PropTypeDescription
targetDateDate | string
onComplete?() => void
className?string