← Back
Animation

Flip Clock

Live real-time clock where each digit flips with a 3D perspective animation every second.

1
5
1
2
0
9
HoursMinutesSeconds

live clock — digit flips every second

FlipClock.tsx86 lines
"use client";

import { useEffect, useRef, useState } from "react";

function pad(n: number) {
  return String(n).padStart(2, "0");
}

function FlipDigit({ value }: { value: string }) {
  const [animKey, setAnimKey] = useState(0);
  const prevRef = useRef(value);

  useEffect(() => {
    if (value !== prevRef.current) {
      prevRef.current = value;
      setAnimKey((k) => k + 1);
    }
  }, [value]);

  return (
    <div className="relative w-14 h-20 flex items-center justify-center bg-zinc-800 dark:bg-zinc-900 rounded-xl shadow-lg overflow-hidden select-none">
      {/* fold crease */}
      <div className="absolute inset-x-0 top-1/2 h-px bg-black/40 z-10" />
      {/* top half slightly darker */}
      <div className="absolute inset-x-0 top-0 h-1/2 bg-black/10" />

      <span
        key={animKey}
        className="text-5xl font-bold font-mono text-white tabular-nums leading-none"
        style={{ animation: animKey > 0 ? "flip-in 0.35s ease-out" : "none" }}
      >
        {value}
      </span>
    </div>
  );
}

function FlipPair({ value }: { value: string }) {
  return (
    <div className="flex gap-1.5">
      <FlipDigit value={value[0]} />
      <FlipDigit value={value[1]} />
    </div>
  );
}

function Colon() {
  return (
    <div className="flex flex-col gap-2 self-center mb-1">
      <div className="w-2 h-2 rounded-full bg-zinc-500 dark:bg-zinc-600" />
      <div className="w-2 h-2 rounded-full bg-zinc-500 dark:bg-zinc-600" />
    </div>
  );
}

export default function FlipClock() {
  const [time, setTime] = useState(new Date());

  useEffect(() => {
    const id = setInterval(() => setTime(new Date()), 1000);
    return () => clearInterval(id);
  }, []);

  return (
    <div className="flex flex-col items-center justify-center gap-6 py-12 w-full">
      <div className="flex items-center gap-3">
        <FlipPair value={pad(time.getHours())}   />
        <Colon />
        <FlipPair value={pad(time.getMinutes())} />
        <Colon />
        <FlipPair value={pad(time.getSeconds())} />
      </div>

      <div className="flex gap-8 text-[10px] font-mono text-zinc-400 dark:text-zinc-600 tracking-widest uppercase">
        <span>Hours</span>
        <span>Minutes</span>
        <span>Seconds</span>
      </div>

      <p className="text-xs font-mono text-zinc-400 dark:text-zinc-600">
        live clock — digit flips every second
      </p>
    </div>
  );
}

More in Animation