Files
components/countdown.tsx
1'use client'
2import React, { useCallback, useEffect, useState } from 'react'
3
4export function Countdown01() {
5    const targetDate = React.useMemo(() => {
6        const now = new Date()
7        now.setDate(now.getDate() + 12)
8        return now.getTime()
9    }, [])
10
11    const calculateTimeLeft = useCallback(() => {
12        const now = Date.now()
13        const diff = targetDate - now
14
15        if (diff <= 0) {
16            return { days: 0, hours: 0, minutes: 0, seconds: 0 }
17        }
18
19        return {
20            days: Math.floor(diff / (1000 * 60 * 60 * 24)),
21            hours: Math.floor((diff / (1000 * 60 * 60)) % 24),
22            minutes: Math.floor((diff / (1000 * 60)) % 60),
23            seconds: Math.floor((diff / 1000) % 60),
24        }
25    }, [targetDate])
26
27    const [mounted, setMounted] = useState(false)
28    const [timeLeft, setTimeLeft] = useState({
29        days: 0,
30        hours: 0,
31        minutes: 0,
32        seconds: 0,
33    })
34
35    useEffect(() => {
36        setMounted(true)
37        setTimeLeft(calculateTimeLeft())
38
39        const timer = setInterval(() => {
40            setTimeLeft(calculateTimeLeft())
41        }, 1000)
42
43        return () => clearInterval(timer)
44    }, [calculateTimeLeft])
45
46    if (!mounted) return null
47
48    return (
49        <div className="flex gap-4">
50            {Object.entries(timeLeft).map(([label, value]) => (
51                <div
52                    key={label}
53                    className="bg-border text-gray flex h-20 w-20 flex-col items-center justify-center rounded-lg"
54                >
55                    <span className="text-2xl font-bold">{value}</span>
56                    <span className="text-xs uppercase">{label}</span>
57                </div>
58            ))}
59        </div>
60    )
61}
62
Countdown displaying remaining time
countdown-01
Files
components/countdown.tsx
1'use client'
2import { useEffect, useState } from 'react'
3import { useForm } from 'react-hook-form'
4import { z } from 'zod'
5
6import { Button } from '@/components/ui/button'
7import { Card, CardContent } from '@/components/ui/card'
8import {
9    Form,
10    FormControl,
11    FormDescription,
12    FormField,
13    FormItem,
14    FormLabel,
15    FormMessage,
16} from '@/components/ui/form'
17import {
18    InputOTP,
19    InputOTPGroup,
20    InputOTPSlot,
21} from '@/components/ui/input-otp'
22import { zodResolver } from '@hookform/resolvers/zod'
23
24const otpSchema = z.object({
25    otp: z
26        .string()
27        .min(6, { message: 'Please enter the 6-digit code.' })
28        .max(6),
29})
30
31export function Countdown02({ ...props }: React.ComponentProps<'div'>) {
32    const form = useForm<z.infer<typeof otpSchema>>({
33        resolver: zodResolver(otpSchema),
34        defaultValues: { otp: '' },
35    })
36
37    const [secondsLeft, setSecondsLeft] = useState(30)
38    const [canResend, setCanResend] = useState(false)
39
40    const AUTO_RESET_DELAY = 10
41
42    useEffect(() => {
43        if (canResend) return
44
45        const timer = setInterval(() => {
46            setSecondsLeft((prev) => {
47                if (prev <= 1) {
48                    setCanResend(true)
49                    return 0
50                }
51                return prev - 1
52            })
53        }, 1000)
54
55        return () => clearInterval(timer)
56    }, [canResend])
57
58    useEffect(() => {
59        if (!canResend) return
60
61        const autoResetTimer = setTimeout(() => {
62            setSecondsLeft(30)
63            setCanResend(false)
64        }, AUTO_RESET_DELAY * 1000)
65
66        return () => clearTimeout(autoResetTimer)
67    }, [canResend])
68
69    const handleResend = () => {
70        console.log('🔁 Resending OTP...')
71        setSecondsLeft(30)
72        setCanResend(false)
73    }
74
75    const onSubmit = (values: z.infer<typeof otpSchema>) => {
76        console.log('✅ OTP Submitted:', values)
77    }
78    return (
79        <Card {...props}>
80            <CardContent>
81                <Form {...form}>
82                    <form
83                        onSubmit={form.handleSubmit(onSubmit)}
84                        className="space-y-3"
85                    >
86                        <FormField
87                            control={form.control}
88                            name="otp"
89                            render={({ field }) => (
90                                <FormItem className="flex flex-col items-center justify-center">
91                                    <FormLabel>Verification code</FormLabel>
92                                    <FormControl>
93                                        <InputOTP
94                                            maxLength={6}
95                                            value={field.value}
96                                            onChange={field.onChange}
97                                            id="otp"
98                                            required
99                                        >
100                                            <InputOTPGroup>
101                                                <InputOTPSlot index={0} />
102                                                <InputOTPSlot index={1} />
103                                                <InputOTPSlot index={2} />
104                                                <InputOTPSlot index={3} />
105                                                <InputOTPSlot index={4} />
106                                                <InputOTPSlot index={5} />
107                                            </InputOTPGroup>
108                                        </InputOTP>
109                                    </FormControl>
110                                    <FormDescription>
111                                        Enter the 6-digit code sent to your
112                                        email.
113                                    </FormDescription>
114                                    <FormMessage />
115                                </FormItem>
116                            )}
117                        />
118
119                        <div className="border-border flex items-center justify-end gap-2 border-t pt-2">
120                            <Button
121                                variant="outline"
122                                disabled={!canResend}
123                                onClick={handleResend}
124                            >
125                                {canResend
126                                    ? 'Resend'
127                                    : `Resend in ${secondsLeft}s`}
128                            </Button>
129                            <Button type="submit">Verify</Button>
130                        </div>
131                    </form>
132                </Form>
133            </CardContent>
134        </Card>
135    )
136}
137
Count down with otp resend
countdown-02
Files
components/countdown.tsx
1'use client'
2
3import React, { useCallback, useEffect, useRef, useState } from 'react'
4
5import { Card, CardContent } from '@/components/ui/card'
6
7const StatCard = ({
8    end,
9    label,
10    prefix = '+',
11    duration = 20000,
12}: {
13    end: number
14    label: string
15    prefix?: string
16    duration?: number
17}) => {
18    const [count, setCount] = useState(0)
19    const elementRef = useRef<HTMLDivElement>(null)
20    const hasAnimatedRef = useRef(false)
21
22    const animateCount = useCallback(() => {
23        let startTime: number | null = null
24        let animationFrameId: number
25
26        const easeOutQuart = (t: number) => 1 - Math.pow(1 - t, 4)
27
28        const animate = (time: number) => {
29            if (!startTime) startTime = time
30
31            const progress = Math.min((time - (startTime ?? 0)) / duration, 1)
32            const easedProgress = easeOutQuart(progress)
33
34            setCount(Math.floor(easedProgress * end))
35
36            if (progress < 1) {
37                animationFrameId = requestAnimationFrame(animate)
38            }
39        }
40
41        animationFrameId = requestAnimationFrame(animate)
42
43        return () => cancelAnimationFrame(animationFrameId)
44    }, [duration, end])
45
46    useEffect(() => {
47        if (!elementRef.current) return
48
49        const observer = new IntersectionObserver(
50            ([entry]) => {
51                if (entry.isIntersecting && !hasAnimatedRef.current) {
52                    hasAnimatedRef.current = true
53                    animateCount()
54                }
55            },
56            { threshold: 0.2 },
57        )
58
59        observer.observe(elementRef.current)
60
61        return () => {
62            observer.disconnect()
63        }
64    }, [animateCount])
65
66    return (
67        <div
68            ref={elementRef}
69            className="flex flex-col items-center justify-center"
70        >
71            <div className="text-primary mb-4 text-5xl font-semibold">
72                {prefix}
73                {count}
74            </div>
75            <div className="text-primary text-xl font-semibold">{label}</div>
76        </div>
77    )
78}
79
80export function Countdown03() {
81    return (
82        <Card className="relative flex items-center justify-center overflow-hidden rounded-2xl border border-gray-200 bg-white py-10 shadow-sm">
83            <CardContent>
84                <div className="relative z-10 w-full max-w-7xl px-4">
85                    <div className="grid grid-cols-2 gap-8 lg:grid-cols-4 lg:gap-12">
86                        <StatCard end={25} label="Partners" />
87                        <StatCard end={55} label="Clients" />
88                        <StatCard end={110} label="Projects" />
89                        <StatCard end={10} label="Experience" prefix="+" />
90                    </div>
91                </div>
92            </CardContent>
93        </Card>
94    )
95}
96
Count down with animated counter
countdown-03
Files
components/countdown.tsx
1'use client'
2import React, { useEffect, useState } from 'react'
3
4export function Countdown04() {
5    const START_VALUE = 8957
6    const [flipping, setFlipping] = useState<Record<number, boolean>>({})
7
8    const [number, setNumber] = useState(START_VALUE)
9    const [prevNumber, setPrevNumber] = useState(START_VALUE)
10
11    useEffect(() => {
12        if (number <= 0) return
13
14        const id = setTimeout(() => {
15            setPrevNumber(number)
16            setNumber((n) => n - 1)
17        }, 1000)
18
19        return () => clearTimeout(id)
20    }, [number])
21
22    useEffect(() => {
23        const digits = number.toString().padStart(4, '0').split('')
24        const prevDigits = prevNumber.toString().padStart(4, '0').split('')
25        const newFlips: Record<number, boolean> = {}
26
27        digits.forEach((digit, index) => {
28            if (digit !== prevDigits[index]) {
29                newFlips[index] = true
30            }
31        })
32
33        if (Object.keys(newFlips).length) {
34            setFlipping((prev) => ({ ...prev, ...newFlips }))
35
36            const timeout = setTimeout(() => {
37                setFlipping({})
38            }, 450)
39
40            return () => clearTimeout(timeout)
41        }
42    }, [number, prevNumber])
43
44    const digits = number.toString().padStart(4, '0').split('')
45    const prevDigits = prevNumber.toString().padStart(4, '0').split('')
46
47    return (
48        <>
49            <style>{`
50            .digit {
51            position: relative;
52            width: 1.6em;
53            height: 2.4em;
54            overflow: hidden;
55            }
56
57            .digit-inner {
58            position: absolute;
59            width: 100%;
60            height: 200%;
61            top: 0;
62            transition: transform 0.45s ease-in-out;
63            will-change: transform;
64            }
65
66            .digit.flip .digit-inner {
67            transform: translateY(-50%);
68            }
69
70            .face {
71            height: 50%;
72            display: flex;
73            align-items: center;
74            justify-content: center;
75            }
76        `}</style>
77
78            <div className="flex items-center justify-center gap-2 text-3xl">
79                <span>$</span>
80
81                {digits.map((digit, index) => {
82                    const prev = prevDigits[index]
83
84                    return (
85                        <div
86                            key={index}
87                            className={`digit ${flipping[index] ? 'flip' : ''} bg-border rounded-lg text-3xl font-bold`}
88                        >
89                            <div className="digit-inner">
90                                <div className="face">{prev}</div>
91                                <div className="face">{digit}</div>
92                            </div>
93                        </div>
94                    )
95                })}
96            </div>
97        </>
98    )
99}
100
countdown block with animated flipping digits.
countdown-04
Files
components/countdown.tsx
1'use client'
2import React, { useEffect, useState } from 'react'
3
4import { Button } from '@/components/ui/button'
5import { Card, CardContent } from '@/components/ui/card'
6import { Progress } from '@/components/ui/progress'
7
8export function Countdown05() {
9    const TOTAL_DURATION = 108 * 60 + 38
10    const TIME_LEFT = 100 * 60 + 38
11    const [timeLeft, setTimeLeft] = useState(TIME_LEFT)
12
13    useEffect(() => {
14        if (timeLeft <= 0) return
15
16        const interval = setInterval(() => {
17            setTimeLeft((prev) => {
18                if (prev <= 1) {
19                    clearInterval(interval)
20                    return 0
21                }
22                return prev - 1
23            })
24        }, 100)
25
26        return () => clearInterval(interval)
27    }, [timeLeft])
28
29    const hours = Math.floor((timeLeft % (24 * 60 * 60)) / (60 * 60))
30    const minutes = Math.floor((timeLeft % (60 * 60)) / 60)
31    const seconds = timeLeft % 60
32
33    const progressPercentage = (timeLeft / TOTAL_DURATION) * 100
34
35    const formatNumber = (num: number): string =>
36        num.toString().padStart(2, '0')
37
38    return (
39        <Card className="flex items-center justify-center">
40            <CardContent>
41                <div className="space-y-4 text-center">
42                    <h2 className="text-xl font-bold sm:text-2xl">
43                        HURRY! ONLY <span className="text-danger">16</span> LEFT
44                        IN STOCK.
45                    </h2>
46
47                    <Progress value={progressPercentage} />
48
49                    <div className="flex items-center justify-center gap-4 sm:gap-8">
50                        <div className="flex flex-col items-center">
51                            <div className="flex gap-1">
52                                {formatNumber(hours)
53                                    .split('')
54                                    .map((digit: string, index: number) => (
55                                        <div
56                                            key={index}
57                                            className="border-border flex h-16 w-12 items-center justify-center rounded-lg border bg-gray-200 shadow-md sm:h-20 sm:w-16"
58                                        >
59                                            <span className="text-4xl font-bold sm:text-5xl">
60                                                {digit}
61                                            </span>
62                                        </div>
63                                    ))}
64                            </div>
65                            <span className="mt-3 text-sm font-medium">
66                                Hours
67                            </span>
68                        </div>
69
70                        <div className="flex flex-col items-center">
71                            <div className="flex gap-1">
72                                {formatNumber(minutes)
73                                    .split('')
74                                    .map((digit: string, index: number) => (
75                                        <div
76                                            key={index}
77                                            className="border-border flex h-16 w-12 items-center justify-center rounded-lg border bg-gray-200 shadow-md sm:h-20 sm:w-16"
78                                        >
79                                            <span className="text-4xl font-bold sm:text-5xl">
80                                                {digit}
81                                            </span>
82                                        </div>
83                                    ))}
84                            </div>
85                            <span className="mt-3 text-sm font-medium">
86                                Mins
87                            </span>
88                        </div>
89
90                        <div className="flex flex-col items-center">
91                            <div className="flex gap-1">
92                                {formatNumber(seconds)
93                                    .split('')
94                                    .map((digit: string, index: number) => (
95                                        <div
96                                            key={index}
97                                            className="border-border flex h-16 w-12 items-center justify-center rounded-lg border bg-gray-200 shadow-md sm:h-20 sm:w-16"
98                                        >
99                                            <span className="text-4xl font-bold sm:text-5xl">
100                                                {digit}
101                                            </span>
102                                        </div>
103                                    ))}
104                            </div>
105                            <span className="mt-3 text-sm font-medium">
106                                Secs
107                            </span>
108                        </div>
109                    </div>
110
111                    <div className="text-center">
112                        <Button>Order Now</Button>
113                    </div>
114                </div>
115
116                <style jsx>{`
117                    @keyframes shimmer {
118                        0% {
119                            transform: translateX(-100%);
120                        }
121                        100% {
122                            transform: translateX(100%);
123                        }
124                    }
125
126                    .animate-shimmer {
127                        animation: shimmer 2s infinite;
128                    }
129                `}</style>
130            </CardContent>
131        </Card>
132    )
133}
134
Count down with progress bar show for left time
countdown-05