UI Components Easy
Live Clock (Analog + Digital)
A comprehensive real-time clock component featuring both a classic analog face and a modern digital readout.
Open in Lab
MCP
vanilla-js css react tailwind svelte vue
Targets: TS JS HTML React Svelte Vue
Code
:root {
--clock-size: 200px;
--clock-bg: rgba(30, 41, 59, 0.8);
--clock-border: rgba(99, 102, 241, 0.3);
--hand-hour: #f8fafc;
--hand-min: #94a3b8;
--hand-sec: #f43f5e;
--clock-glow: rgba(99, 102, 241, 0.2);
}
.live-clock-widget {
display: flex;
flex-direction: column;
align-items: center;
gap: 1.5rem;
padding: 2rem;
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 24px;
font-family: "Inter", system-ui, sans-serif;
backdrop-filter: blur(16px);
width: fit-content;
margin: 0 auto;
}
.clock-face {
width: var(--clock-size);
height: var(--clock-size);
background: var(--clock-bg);
border: 2px solid var(--clock-border);
border-radius: 50%;
position: relative;
box-shadow: inset 0 0 30px rgba(0, 0, 0, 0.4), 0 0 40px var(--clock-glow), 0 8px 32px
rgba(0, 0, 0, 0.5);
}
.center-dot {
position: absolute;
top: 50%;
left: 50%;
width: 10px;
height: 10px;
background: #f43f5e;
border-radius: 50%;
transform: translate(-50%, -50%);
z-index: 10;
box-shadow: 0 0 8px rgba(244, 63, 94, 0.6);
}
.hand {
position: absolute;
bottom: 50%;
left: 50%;
transform-origin: bottom center;
border-radius: 4px;
transition: transform 0.5s cubic-bezier(0.4, 2.08, 0.55, 0.44);
}
.hand.hour {
width: 5px;
height: 52px;
background: var(--hand-hour);
margin-left: -2.5px;
z-index: 3;
border-radius: 4px 4px 2px 2px;
box-shadow: 0 0 8px rgba(255, 255, 255, 0.2);
}
.hand.minute {
width: 3px;
height: 74px;
background: var(--hand-min);
margin-left: -1.5px;
z-index: 2;
border-radius: 4px 4px 2px 2px;
}
.hand.second {
width: 2px;
height: 86px;
background: var(--hand-sec);
margin-left: -1px;
z-index: 4;
border-radius: 4px 4px 1px 1px;
box-shadow: 0 0 8px rgba(244, 63, 94, 0.5);
}
/* Clock tick marks */
.clock-face::before {
content: "";
position: absolute;
inset: 8px;
border-radius: 50%;
border: 1px dashed rgba(255, 255, 255, 0.06);
}
.numbers {
position: absolute;
width: 100%;
height: 100%;
color: rgba(148, 163, 184, 0.6);
font-weight: 700;
font-size: 1rem;
font-family: "Inter", system-ui, sans-serif;
}
.numbers span {
position: absolute;
}
.numbers span:nth-child(1) {
top: 10px;
left: 50%;
transform: translateX(-50%);
}
.numbers span:nth-child(2) {
right: 12px;
top: 50%;
transform: translateY(-50%);
}
.numbers span:nth-child(3) {
bottom: 10px;
left: 50%;
transform: translateX(-50%);
}
.numbers span:nth-child(4) {
left: 12px;
top: 50%;
transform: translateY(-50%);
}
.digital-readout {
font-size: 1.625rem;
font-weight: 800;
color: #f8fafc;
letter-spacing: 0.06em;
font-variant-numeric: tabular-nums;
font-family: "JetBrains Mono", "Courier New", monospace;
}
#digital-ampm {
font-size: 0.875rem;
color: #64748b;
margin-left: 0.35rem;
font-weight: 600;
font-family: "Inter", sans-serif;
}function updateLiveClock() {
const now = new Date();
const seconds = now.getSeconds();
const minutes = now.getMinutes();
const hours = now.getHours();
// Analog calculations
const secondDeg = (seconds / 60) * 360;
const minuteDeg = (minutes / 60) * 360 + (seconds / 60) * 6;
const hourDeg = (hours / 12) * 360 + (minutes / 60) * 30;
document.getElementById("second-hand").style.transform =
`translateX(-50%) rotate(${secondDeg}deg)`;
document.getElementById("minute-hand").style.transform =
`translateX(-50%) rotate(${minuteDeg}deg)`;
document.getElementById("hour-hand").style.transform = `translateX(-50%) rotate(${hourDeg}deg)`;
// Digital readout
const displayHours = hours % 12 || 12;
const ampm = hours >= 12 ? "PM" : "AM";
const displayTime = `${displayHours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
document.getElementById("digital-time").textContent = displayTime;
document.getElementById("digital-ampm").textContent = ampm;
}
setInterval(updateLiveClock, 1000);
updateLiveClock();<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Live Clock</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="live-clock-widget">
<div class="clock-face">
<div class="hand hour" id="hour-hand"></div>
<div class="hand minute" id="minute-hand"></div>
<div class="hand second" id="second-hand"></div>
<div class="center-dot"></div>
<div class="numbers">
<span>12</span>
<span>3</span>
<span>6</span>
<span>9</span>
</div>
</div>
<div class="digital-readout">
<span id="digital-time">00:00:00</span>
<span id="digital-ampm">AM</span>
</div>
</div>
<script src="script.js"></script>
</body>
</html>import { useState, useEffect } from "react";
function AnalogClock({ date }: { date: Date }) {
const h = date.getHours() % 12;
const m = date.getMinutes();
const s = date.getSeconds();
const hourDeg = (h / 12) * 360 + (m / 60) * 30;
const minDeg = (m / 60) * 360 + (s / 60) * 6;
const secDeg = (s / 60) * 360;
return (
<svg viewBox="0 0 200 200" className="w-48 h-48">
<circle cx="100" cy="100" r="96" fill="#161b22" stroke="#30363d" strokeWidth="2" />
{Array.from({ length: 12 }, (_, i) => {
const angle = ((i / 12) * 360 * Math.PI) / 180;
const x1 = 100 + 80 * Math.sin(angle);
const y1 = 100 - 80 * Math.cos(angle);
const x2 = 100 + 88 * Math.sin(angle);
const y2 = 100 - 88 * Math.cos(angle);
return (
<line
key={i}
x1={x1}
y1={y1}
x2={x2}
y2={y2}
stroke="#484f58"
strokeWidth="2.5"
strokeLinecap="round"
/>
);
})}
{/* Hour hand */}
<line
x1="100"
y1="100"
x2={100 + 50 * Math.sin((hourDeg * Math.PI) / 180)}
y2={100 - 50 * Math.cos((hourDeg * Math.PI) / 180)}
stroke="#e6edf3"
strokeWidth="4"
strokeLinecap="round"
/>
{/* Minute hand */}
<line
x1="100"
y1="100"
x2={100 + 70 * Math.sin((minDeg * Math.PI) / 180)}
y2={100 - 70 * Math.cos((minDeg * Math.PI) / 180)}
stroke="#e6edf3"
strokeWidth="2.5"
strokeLinecap="round"
/>
{/* Second hand */}
<line
x1="100"
y1="100"
x2={100 + 75 * Math.sin((secDeg * Math.PI) / 180)}
y2={100 - 75 * Math.cos((secDeg * Math.PI) / 180)}
stroke="#f85149"
strokeWidth="1.5"
strokeLinecap="round"
/>
<circle cx="100" cy="100" r="4" fill="#f85149" />
</svg>
);
}
export default function LiveClockRC() {
const [now, setNow] = useState(new Date());
useEffect(() => {
const id = setInterval(() => setNow(new Date()), 1000);
return () => clearInterval(id);
}, []);
const timeStr = now.toLocaleTimeString(undefined, {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
});
const dateStr = now.toLocaleDateString(undefined, {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
});
const zones = [
{ city: "New York", offset: -5 },
{ city: "London", offset: 0 },
{ city: "Tokyo", offset: 9 },
];
return (
<div className="min-h-screen bg-[#0d1117] flex flex-col items-center justify-center gap-6 p-6">
<div className="flex flex-col items-center gap-2">
<AnalogClock date={now} />
<p className="font-mono text-[36px] font-bold text-[#e6edf3] tabular-nums">{timeStr}</p>
<p className="text-[#8b949e] text-sm">{dateStr}</p>
</div>
<div className="flex gap-4">
{zones.map(({ city, offset }) => {
const cityTime = new Date(
now.getTime() + (now.getTimezoneOffset() + offset * 60) * 60000
);
return (
<div
key={city}
className="bg-[#161b22] border border-[#30363d] rounded-xl px-4 py-3 text-center"
>
<p className="text-[11px] text-[#484f58] uppercase tracking-wider mb-1">{city}</p>
<p className="font-mono text-[14px] font-bold text-[#e6edf3] tabular-nums">
{cityTime.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit" })}
</p>
</div>
);
})}
</div>
</div>
);
}<script>
import { onMount, onDestroy } from "svelte";
let now = new Date();
let intervalId;
onMount(() => {
intervalId = setInterval(() => {
now = new Date();
}, 1000);
});
onDestroy(() => {
if (intervalId) clearInterval(intervalId);
});
$: h = now.getHours() % 12;
$: m = now.getMinutes();
$: s = now.getSeconds();
$: hourDeg = (h / 12) * 360 + (m / 60) * 30;
$: minDeg = (m / 60) * 360 + (s / 60) * 6;
$: secDeg = (s / 60) * 360;
$: timeStr = now.toLocaleTimeString(undefined, {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
});
$: dateStr = now.toLocaleDateString(undefined, {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
});
const zones = [
{ city: "New York", offset: -5 },
{ city: "London", offset: 0 },
{ city: "Tokyo", offset: 9 },
];
$: ticks = Array.from({ length: 12 }, (_, i) => {
const angle = ((i / 12) * 360 * Math.PI) / 180;
return {
x1: 100 + 80 * Math.sin(angle),
y1: 100 - 80 * Math.cos(angle),
x2: 100 + 88 * Math.sin(angle),
y2: 100 - 88 * Math.cos(angle),
};
});
function cityTime(offset) {
return new Date(now.getTime() + (now.getTimezoneOffset() + offset * 60) * 60000);
}
</script>
<div class="min-h-screen bg-[#0d1117] flex flex-col items-center justify-center gap-6 p-6">
<div class="flex flex-col items-center gap-2">
<!-- Analog Clock -->
<svg viewBox="0 0 200 200" class="w-48 h-48">
<circle cx="100" cy="100" r="96" fill="#161b22" stroke="#30363d" stroke-width="2" />
{#each ticks as tick, i}
<line
x1={tick.x1} y1={tick.y1} x2={tick.x2} y2={tick.y2}
stroke="#484f58" stroke-width="2.5" stroke-linecap="round"
/>
{/each}
<!-- Hour hand -->
<line
x1="100" y1="100"
x2={100 + 50 * Math.sin((hourDeg * Math.PI) / 180)}
y2={100 - 50 * Math.cos((hourDeg * Math.PI) / 180)}
stroke="#e6edf3" stroke-width="4" stroke-linecap="round"
/>
<!-- Minute hand -->
<line
x1="100" y1="100"
x2={100 + 70 * Math.sin((minDeg * Math.PI) / 180)}
y2={100 - 70 * Math.cos((minDeg * Math.PI) / 180)}
stroke="#e6edf3" stroke-width="2.5" stroke-linecap="round"
/>
<!-- Second hand -->
<line
x1="100" y1="100"
x2={100 + 75 * Math.sin((secDeg * Math.PI) / 180)}
y2={100 - 75 * Math.cos((secDeg * Math.PI) / 180)}
stroke="#f85149" stroke-width="1.5" stroke-linecap="round"
/>
<circle cx="100" cy="100" r="4" fill="#f85149" />
</svg>
<p class="font-mono text-[36px] font-bold text-[#e6edf3] tabular-nums">{timeStr}</p>
<p class="text-[#8b949e] text-sm">{dateStr}</p>
</div>
<div class="flex gap-4">
{#each zones as zone}
<div class="bg-[#161b22] border border-[#30363d] rounded-xl px-4 py-3 text-center">
<p class="text-[11px] text-[#484f58] uppercase tracking-wider mb-1">{zone.city}</p>
<p class="font-mono text-[14px] font-bold text-[#e6edf3] tabular-nums">
{cityTime(zone.offset).toLocaleTimeString(undefined, {
hour: "2-digit",
minute: "2-digit",
})}
</p>
</div>
{/each}
</div>
</div><script setup>
import { ref, computed, onMounted, onUnmounted } from "vue";
const now = ref(new Date());
let intervalId = null;
onMounted(() => {
intervalId = setInterval(() => {
now.value = new Date();
}, 1000);
});
onUnmounted(() => {
if (intervalId) clearInterval(intervalId);
});
const h = computed(() => now.value.getHours() % 12);
const m = computed(() => now.value.getMinutes());
const s = computed(() => now.value.getSeconds());
const hourDeg = computed(() => (h.value / 12) * 360 + (m.value / 60) * 30);
const minDeg = computed(() => (m.value / 60) * 360 + (s.value / 60) * 6);
const secDeg = computed(() => (s.value / 60) * 360);
const timeStr = computed(() =>
now.value.toLocaleTimeString(undefined, {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
})
);
const dateStr = computed(() =>
now.value.toLocaleDateString(undefined, {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
})
);
const ticks = computed(() =>
Array.from({ length: 12 }, (_, i) => {
const angle = ((i / 12) * 360 * Math.PI) / 180;
return {
x1: 100 + 80 * Math.sin(angle),
y1: 100 - 80 * Math.cos(angle),
x2: 100 + 88 * Math.sin(angle),
y2: 100 - 88 * Math.cos(angle),
};
})
);
const zones = [
{ city: "New York", offset: -5 },
{ city: "London", offset: 0 },
{ city: "Tokyo", offset: 9 },
];
function cityTime(offset) {
return new Date(now.value.getTime() + (now.value.getTimezoneOffset() + offset * 60) * 60000);
}
</script>
<template>
<div class="min-h-screen bg-[#0d1117] flex flex-col items-center justify-center gap-6 p-6">
<div class="flex flex-col items-center gap-2">
<!-- Analog Clock -->
<svg viewBox="0 0 200 200" class="w-48 h-48">
<circle cx="100" cy="100" r="96" fill="#161b22" stroke="#30363d" stroke-width="2" />
<line
v-for="(tick, i) in ticks"
:key="i"
:x1="tick.x1" :y1="tick.y1" :x2="tick.x2" :y2="tick.y2"
stroke="#484f58" stroke-width="2.5" stroke-linecap="round"
/>
<!-- Hour hand -->
<line
x1="100" y1="100"
:x2="100 + 50 * Math.sin((hourDeg * Math.PI) / 180)"
:y2="100 - 50 * Math.cos((hourDeg * Math.PI) / 180)"
stroke="#e6edf3" stroke-width="4" stroke-linecap="round"
/>
<!-- Minute hand -->
<line
x1="100" y1="100"
:x2="100 + 70 * Math.sin((minDeg * Math.PI) / 180)"
:y2="100 - 70 * Math.cos((minDeg * Math.PI) / 180)"
stroke="#e6edf3" stroke-width="2.5" stroke-linecap="round"
/>
<!-- Second hand -->
<line
x1="100" y1="100"
:x2="100 + 75 * Math.sin((secDeg * Math.PI) / 180)"
:y2="100 - 75 * Math.cos((secDeg * Math.PI) / 180)"
stroke="#f85149" stroke-width="1.5" stroke-linecap="round"
/>
<circle cx="100" cy="100" r="4" fill="#f85149" />
</svg>
<p class="font-mono text-[36px] font-bold text-[#e6edf3] tabular-nums">{{ timeStr }}</p>
<p class="text-[#8b949e] text-sm">{{ dateStr }}</p>
</div>
<div class="flex gap-4">
<div
v-for="zone in zones"
:key="zone.city"
class="bg-[#161b22] border border-[#30363d] rounded-xl px-4 py-3 text-center"
>
<p class="text-[11px] text-[#484f58] uppercase tracking-wider mb-1">{{ zone.city }}</p>
<p class="font-mono text-[14px] font-bold text-[#e6edf3] tabular-nums">
{{ cityTime(zone.offset).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' }) }}
</p>
</div>
</div>
</div>
</template>Live Clock (Analog + Digital)
A decorative and functional timekeeping component. It combines the elegance of a traditional analog clock—with moving hour, minute, and second hands—with the precision of a digital display.
Features
- Real-time synchronization with system clock
- CSS-driven analog hand rotations
- Localized digital time formatting
- Glassmorphism design system
- Smooth, non-repeating animations