Open demo

CSS code

HTML
<div><span class="lbl">Classic, minimal and Roman</span><div class="row">
<div class="clock" data-analogue role="img" aria-label="Analogue clock">
  <b style="--i:0">12</b><b style="--i:1">1</b><b style="--i:2">2</b><b style="--i:3">3</b><b style="--i:4">4</b><b style="--i:5">5</b><b style="--i:6">6</b><b style="--i:7">7</b><b style="--i:8">8</b><b style="--i:9">9</b><b style="--i:10">10</b><b style="--i:11">11</b>
  <span class="h"></span><span class="m"></span><span class="s"></span><span class="pin"></span>
</div>
<div class="clock minimal" data-analogue role="img" aria-label="Analogue clock">
  <b style="--i:0">12</b><i style="--i:1"></i><i style="--i:2"></i><b style="--i:3">3</b><i style="--i:4"></i><i style="--i:5"></i><b style="--i:6">6</b><i style="--i:7"></i><i style="--i:8"></i><b style="--i:9">9</b><i style="--i:10"></i><i style="--i:11"></i>
  <span class="h"></span><span class="m"></span><span class="s"></span><span class="pin"></span>
</div>
<div class="clock roman" data-analogue role="img" aria-label="Analogue clock">
  <b style="--i:0">XII</b><b style="--i:1">I</b><b style="--i:2">II</b><b style="--i:3">III</b><b style="--i:4">IV</b><b style="--i:5">V</b><b style="--i:6">VI</b><b style="--i:7">VII</b><b style="--i:8">VIII</b><b style="--i:9">IX</b><b style="--i:10">X</b><b style="--i:11">XI</b>
  <span class="h"></span><span class="m"></span><span class="s"></span><span class="pin"></span>
</div>
</div></div>
CSS
/* Analogue clocks: CSS draws the face, numerals and hands. The script sets --h, --m and --s (angles)
   on each [data-analogue] face, and the hands rotate by them. Numerals sit on a ring via --i (0-11). */
.clock{--ink:#0f172a;--hand-s:#dc2626;--tick:#94a3b8;--face:#fff;--rim:#0f172a;--num-font:inherit;
  position:relative;flex:none;width:180px;height:180px;border-radius:50%;background:var(--face);
  border:6px solid var(--rim);box-shadow:0 2px 14px rgba(15,23,42,.12)}
.clock b{position:absolute;left:50%;top:50%;font-size:.85rem;font-weight:700;line-height:1;color:var(--ink);
  font-family:var(--num-font);transform:translate(-50%,-50%) rotate(calc(var(--i)*30deg)) translateY(-68px) rotate(calc(var(--i)*-30deg))}
.clock i{position:absolute;left:50%;top:6px;width:2px;height:9px;margin-left:-1px;background:var(--tick);
  transform-origin:50% 78px;transform:rotate(calc(var(--i)*30deg))}
.clock .h,.clock .m,.clock .s{position:absolute;left:50%;bottom:50%;transform-origin:50% 100%;border-radius:3px;background:var(--ink)}
.clock .h{width:6px;height:48px;margin-left:-3px;transform:rotate(var(--h,0deg))}
.clock .m{width:4px;height:68px;margin-left:-2px;transform:rotate(var(--m,0deg))}
.clock .s{width:2px;height:76px;margin-left:-1px;background:var(--hand-s);transform:rotate(var(--s,0deg))}
.clock .pin{position:absolute;left:50%;top:50%;width:11px;height:11px;margin:-5.5px 0 0 -5.5px;border-radius:50%;background:var(--hand-s)}
/* minimal: thin grey rim, blue second hand, four numerals and eight ticks */
.clock.minimal{--hand-s:#2563eb;--rim:#e2e8f0;border-width:2px;box-shadow:0 2px 14px rgba(15,23,42,.07)}
/* Roman: warm face, brown rim and hands, serif numerals */
.clock.roman{--ink:#3f2d20;--hand-s:#8c6239;--rim:#8c6239;--face:#fdfaf3;--num-font:Georgia,"Times New Roman",serif}
JavaScript
// Analogue clocks. Every [data-analogue] face gets --h, --m and --s angles from the system time,
// re-timed to each second boundary. Each face is role="img"; its name ("Clock showing 2:41 PM")
// changes once a minute and is never announced on its own, so screen readers are not interrupted.
(function () {
  var faces = document.querySelectorAll('[data-analogue]');
  if (!faces.length) return;
  var lastMinute = -1;
  function tick() {
    var d = new Date(), s = d.getSeconds(), m = d.getMinutes(), h = d.getHours() % 12;
    var name = m !== lastMinute && 'Clock showing ' + d.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });
    lastMinute = m;
    faces.forEach(function (face) {
      face.style.setProperty('--s', s * 6 + 'deg');
      face.style.setProperty('--m', m * 6 + s * 0.1 + 'deg');
      face.style.setProperty('--h', h * 30 + m * 0.5 + 'deg');
      if (name) face.setAttribute('aria-label', name);
    });
    setTimeout(tick, 1000 - Date.now() % 1000);
  }
  tick();
})();

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div><span class="lbl">Classic, minimal and Roman</span><div class="d-flex flex-wrap align-items-center" style="gap:34px">
<div class="clock position-relative rounded-circle border" data-analogue role="img" aria-label="Analogue clock">
  <b style="--i:0">12</b><b style="--i:1">1</b><b style="--i:2">2</b><b style="--i:3">3</b><b style="--i:4">4</b><b style="--i:5">5</b><b style="--i:6">6</b><b style="--i:7">7</b><b style="--i:8">8</b><b style="--i:9">9</b><b style="--i:10">10</b><b style="--i:11">11</b>
  <span class="clock-hand h"></span><span class="clock-hand m"></span><span class="clock-hand s"></span><span class="clock-pin position-absolute top-50 start-50 translate-middle rounded-circle"></span>
</div>
<div class="clock minimal position-relative rounded-circle border" data-analogue role="img" aria-label="Analogue clock">
  <b style="--i:0">12</b><i style="--i:1"></i><i style="--i:2"></i><b style="--i:3">3</b><i style="--i:4"></i><i style="--i:5"></i><b style="--i:6">6</b><i style="--i:7"></i><i style="--i:8"></i><b style="--i:9">9</b><i style="--i:10"></i><i style="--i:11"></i>
  <span class="clock-hand h"></span><span class="clock-hand m"></span><span class="clock-hand s"></span><span class="clock-pin position-absolute top-50 start-50 translate-middle rounded-circle"></span>
</div>
<div class="clock roman position-relative rounded-circle border" data-analogue role="img" aria-label="Analogue clock">
  <b style="--i:0">XII</b><b style="--i:1">I</b><b style="--i:2">II</b><b style="--i:3">III</b><b style="--i:4">IV</b><b style="--i:5">V</b><b style="--i:6">VI</b><b style="--i:7">VII</b><b style="--i:8">VIII</b><b style="--i:9">IX</b><b style="--i:10">X</b><b style="--i:11">XI</b>
  <span class="clock-hand h"></span><span class="clock-hand m"></span><span class="clock-hand s"></span><span class="clock-pin position-absolute top-50 start-50 translate-middle rounded-circle"></span>
</div>
</div></div>
CSS
/* Analogue clocks on Bootstrap: .rounded-circle, .border and .position-relative build the face, the colours
   come from --bs-border-color / --bs-border-width, and the hands rotate by --h, --m and --s set by the script. */
.clock{--ink:#0f172a;--hand-s:#dc2626;--tick:#94a3b8;--face:#fff;--num-font:inherit;
  --bs-border-color:#0f172a;--bs-border-width:6px;
  flex:none;width:180px;height:180px;background:var(--face);box-shadow:0 2px 14px rgba(15,23,42,.12)}
.clock b{position:absolute;left:50%;top:50%;font-size:.85rem;font-weight:700;line-height:1;color:var(--ink);
  font-family:var(--num-font);transform:translate(-50%,-50%) rotate(calc(var(--i)*30deg)) translateY(-68px) rotate(calc(var(--i)*-30deg))}
.clock i{position:absolute;left:50%;top:6px;width:2px;height:9px;margin-left:-1px;background:var(--tick);
  transform-origin:50% 78px;transform:rotate(calc(var(--i)*30deg))}
.clock-hand{position:absolute;left:50%;bottom:50%;transform-origin:50% 100%;border-radius:3px;background:var(--ink)}
.clock-hand.h{width:6px;height:48px;margin-left:-3px;transform:rotate(var(--h,0deg))}
.clock-hand.m{width:4px;height:68px;margin-left:-2px;transform:rotate(var(--m,0deg))}
.clock-hand.s{width:2px;height:76px;margin-left:-1px;background:var(--hand-s);transform:rotate(var(--s,0deg))}
.clock-pin{width:11px;height:11px;background:var(--hand-s)}
.clock.minimal{--hand-s:#2563eb;--bs-border-color:#e2e8f0;--bs-border-width:2px;box-shadow:0 2px 14px rgba(15,23,42,.07)}
.clock.roman{--ink:#3f2d20;--hand-s:#8c6239;--bs-border-color:#8c6239;--face:#fdfaf3;--num-font:Georgia,"Times New Roman",serif}
JavaScript
// Analogue clocks. Every [data-analogue] face gets --h, --m and --s angles from the system time,
// re-timed to each second boundary. Each face is role="img"; its name ("Clock showing 2:41 PM")
// changes once a minute and is never announced on its own, so screen readers are not interrupted.
(function () {
  var faces = document.querySelectorAll('[data-analogue]');
  if (!faces.length) return;
  var lastMinute = -1;
  function tick() {
    var d = new Date(), s = d.getSeconds(), m = d.getMinutes(), h = d.getHours() % 12;
    var name = m !== lastMinute && 'Clock showing ' + d.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });
    lastMinute = m;
    faces.forEach(function (face) {
      face.style.setProperty('--s', s * 6 + 'deg');
      face.style.setProperty('--m', m * 6 + s * 0.1 + 'deg');
      face.style.setProperty('--h', h * 30 + m * 0.5 + 'deg');
      if (name) face.setAttribute('aria-label', name);
    });
    setTimeout(tick, 1000 - Date.now() % 1000);
  }
  tick();
})();

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#64748b] uppercase">Classic, minimal and Roman</span><div class="flex flex-wrap items-center gap-[34px]">
<div class="clock-numerals relative size-[180px] flex-none rounded-full border-6 border-[#0f172a] bg-white text-[#0f172a] shadow-[0_2px_14px_rgba(15,23,42,.12)]" data-analogue role="img" aria-label="Analogue clock">
  <b style="--i:0">12</b><b style="--i:1">1</b><b style="--i:2">2</b><b style="--i:3">3</b><b style="--i:4">4</b><b style="--i:5">5</b><b style="--i:6">6</b><b style="--i:7">7</b><b style="--i:8">8</b><b style="--i:9">9</b><b style="--i:10">10</b><b style="--i:11">11</b>
  <span class="absolute bottom-1/2 left-1/2 -ml-[3px] h-12 w-1.5 origin-bottom rotate-(--h) rounded-[3px] bg-[#0f172a]"></span><span class="absolute bottom-1/2 left-1/2 -ml-0.5 h-[68px] w-1 origin-bottom rotate-(--m) rounded-[3px] bg-[#0f172a]"></span><span class="absolute bottom-1/2 left-1/2 -ml-px h-[76px] w-0.5 origin-bottom rotate-(--s) rounded-[3px] bg-[#dc2626]"></span><span class="absolute top-1/2 left-1/2 size-[11px] -translate-1/2 rounded-full bg-[#dc2626]"></span>
</div>
<div class="clock-numerals relative size-[180px] flex-none rounded-full border-2 border-[#e2e8f0] bg-white text-[#0f172a] shadow-[0_2px_14px_rgba(15,23,42,.07)]" data-analogue role="img" aria-label="Analogue clock">
  <b style="--i:0">12</b><i style="--i:1"></i><i style="--i:2"></i><b style="--i:3">3</b><i style="--i:4"></i><i style="--i:5"></i><b style="--i:6">6</b><i style="--i:7"></i><i style="--i:8"></i><b style="--i:9">9</b><i style="--i:10"></i><i style="--i:11"></i>
  <span class="absolute bottom-1/2 left-1/2 -ml-[3px] h-12 w-1.5 origin-bottom rotate-(--h) rounded-[3px] bg-[#0f172a]"></span><span class="absolute bottom-1/2 left-1/2 -ml-0.5 h-[68px] w-1 origin-bottom rotate-(--m) rounded-[3px] bg-[#0f172a]"></span><span class="absolute bottom-1/2 left-1/2 -ml-px h-[76px] w-0.5 origin-bottom rotate-(--s) rounded-[3px] bg-[#2563eb]"></span><span class="absolute top-1/2 left-1/2 size-[11px] -translate-1/2 rounded-full bg-[#2563eb]"></span>
</div>
<div class="clock-numerals relative size-[180px] flex-none rounded-full border-6 border-[#8c6239] bg-[#fdfaf3] font-[Georgia,'Times_New_Roman',serif] text-[#3f2d20] shadow-[0_2px_14px_rgba(15,23,42,.12)]" data-analogue role="img" aria-label="Analogue clock">
  <b style="--i:0">XII</b><b style="--i:1">I</b><b style="--i:2">II</b><b style="--i:3">III</b><b style="--i:4">IV</b><b style="--i:5">V</b><b style="--i:6">VI</b><b style="--i:7">VII</b><b style="--i:8">VIII</b><b style="--i:9">IX</b><b style="--i:10">X</b><b style="--i:11">XI</b>
  <span class="absolute bottom-1/2 left-1/2 -ml-[3px] h-12 w-1.5 origin-bottom rotate-(--h) rounded-[3px] bg-[#3f2d20]"></span><span class="absolute bottom-1/2 left-1/2 -ml-0.5 h-[68px] w-1 origin-bottom rotate-(--m) rounded-[3px] bg-[#3f2d20]"></span><span class="absolute bottom-1/2 left-1/2 -ml-px h-[76px] w-0.5 origin-bottom rotate-(--s) rounded-[3px] bg-[#8c6239]"></span><span class="absolute top-1/2 left-1/2 size-[11px] -translate-1/2 rounded-full bg-[#8c6239]"></span>
</div>
</div></div>
CSS
/* Numerals sit on a 68px ring: each <b> has --i (0-11), the face places it with one transform. */
@utility clock-numerals {
  & > b { position:absolute; left:50%; top:50%; font-size:.85rem; font-weight:700; line-height:1;
    transform:translate(-50%,-50%) rotate(calc(var(--i)*30deg)) translateY(-68px) rotate(calc(var(--i)*-30deg)); }
  & > i { position:absolute; left:50%; top:6px; width:2px; height:9px; margin-left:-1px; background:#94a3b8;
    transform-origin:50% 78px; transform:rotate(calc(var(--i)*30deg)); }
}
JavaScript
// Analogue clocks. Every [data-analogue] face gets --h, --m and --s angles from the system time,
// re-timed to each second boundary. Each face is role="img"; its name ("Clock showing 2:41 PM")
// changes once a minute and is never announced on its own, so screen readers are not interrupted.
(function () {
  var faces = document.querySelectorAll('[data-analogue]');
  if (!faces.length) return;
  var lastMinute = -1;
  function tick() {
    var d = new Date(), s = d.getSeconds(), m = d.getMinutes(), h = d.getHours() % 12;
    var name = m !== lastMinute && 'Clock showing ' + d.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });
    lastMinute = m;
    faces.forEach(function (face) {
      face.style.setProperty('--s', s * 6 + 'deg');
      face.style.setProperty('--m', m * 6 + s * 0.1 + 'deg');
      face.style.setProperty('--h', h * 30 + m * 0.5 + 'deg');
      if (name) face.setAttribute('aria-label', name);
    });
    setTimeout(tick, 1000 - Date.now() % 1000);
  }
  tick();
})();

About this set

Three analogue clock faces drawn entirely in CSS and set from the system time: a classic face with a dark rim and the numerals 1 to 12, a minimal face with a thin grey rim, four numerals and tick marks in between, and a Roman-numeral wall clock with a warm cream face and brown hands. Use the classic face where a clock should read at a glance, the minimal one in a light interface that needs a quieter object, and the Roman one for editorial, hospitality or vintage designs. Each numeral is an element with a –i index from 0 to 11, and a single transform places it on a 68 pixel ring while keeping the text upright, so there is no trigonometry in the script. The hands are three bars pivoting at the centre. A short vanilla script sets –h, –m and –s angles on every face once a second, re-timed to the second boundary, and nothing else. The Bootstrap version builds the face from .rounded-circle, .border and positioning utilities, and the Tailwind version uses rotate utilities that read the same custom properties.

What’s included

  • Classic, minimal and Roman-numeral faces
  • Numerals placed on a ring with one CSS transform per --i index
  • Hands rotate from --h, --m and --s custom properties set by the script
  • Each face is role="img" with a name that updates once a minute
  • Plain CSS, Bootstrap and Tailwind versions of the same faces

Accessibility

Each face is role="img" with an accessible name such as "Clock showing 2:41 PM", rewritten only when the minute changes, so the decorative numerals, ticks and hands are not read one by one. The name is not a live region, so screen readers are never interrupted. The clocks have no controls and no animation; the hands jump once a second.

Customise it

Change --ink, --hand-s, --rim, --face, --tick and --num-font on .clock in plain CSS. In Bootstrap the same variables sit on .clock with --bs-border-color and --bs-border-width for the rim; in Tailwind edit the border-, bg- and text- colour utilities on each face and its hands.