Open demo

CSS code

HTML
<div><span class="lbl">Dashboard tiles</span>
<div class="tiles" data-widgets>
  <div class="tile"><h3>Local time</h3><b data-show="time">--:--</b><span data-show="date">—</span></div>
  <div class="tile"><h3>Working day</h3><b data-show="pct">—</b>
    <div class="bar" role="progressbar" aria-label="Working day" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"><i data-bar style="width:0"></i></div>
    <span data-show="left">—</span></div>
  <div class="tile"><h3>Status</h3><div class="stamp"><i aria-hidden="true"></i><span data-show="stamp">Updated just now</span></div></div>
</div></div>
CSS
/* Dashboard clock tiles. The script fills [data-show] (time, date, pct, left, stamp) inside [data-widgets],
   sizes the [data-bar] fill and keeps the working-day progressbar's aria values in step. */
.tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(215px,1fr));gap:15px}
.tile{background:#fff;border-radius:13px;padding:18px 20px;box-shadow:0 2px 10px rgba(15,23,42,.09)}
.tile h3{font-size:.7rem;letter-spacing:.12em;text-transform:uppercase;color:#64748b;margin-bottom:11px}
.tile b{font-size:1.75rem;font-weight:600;font-variant-numeric:tabular-nums;display:block;line-height:1}
.tile span{font-size:.78rem;color:#64748b;display:block;margin-top:7px}
.tile .bar{height:7px;border-radius:99px;background:#e2e8f0;margin-top:13px;overflow:hidden}
.tile .bar i{display:block;height:100%;background:#2563eb;border-radius:99px;transition:width .5s}
.tile .stamp{display:inline-flex;align-items:center;gap:8px;font-size:.82rem;color:#475569}
.tile .stamp i{width:7px;height:7px;border-radius:50%;background:#16a34a;display:block}
.tile .stamp span{font-size:inherit;color:inherit;margin:0}
@media (prefers-reduced-motion:reduce){.tile .bar i{transition:none}}
JavaScript
// Dashboard clock tiles: local time and date, progress through the working day (START to END, local hours)
// and a "last updated" stamp. Re-timed to each second boundary; text only changes when its value does.
(function () {
  var roots = document.querySelectorAll('[data-widgets]');
  if (!roots.length) return;
  var START = 9, END = 18;
  function p2(n) { return String(n).padStart(2, '0'); }
  function set(el, text) { if (el && el.textContent !== text) el.textContent = text; }
  function tick() {
    var d = new Date(), H = d.getHours(), M = d.getMinutes();
    var mins = H * 60 + M, from = START * 60, to = END * 60;
    var pct = Math.max(0, Math.min(100, (mins - from) / (to - from) * 100));
    var left = pct <= 0 ? 'Not started' : pct >= 100 ? 'Finished for the day'
      : Math.floor((to - mins) / 60) + 'h ' + (to - mins) % 60 + 'm remaining';
    var out = {
      time: p2(H) + ':' + p2(M),
      date: d.toLocaleDateString(undefined, { weekday: 'long', day: 'numeric', month: 'short' }),
      pct: Math.round(pct) + '%', left: left,
      stamp: 'Updated ' + p2(H) + ':' + p2(M) + ':' + p2(d.getSeconds())
    };
    roots.forEach(function (root) {
      root.querySelectorAll('[data-show]').forEach(function (el) { set(el, out[el.getAttribute('data-show')]); });
      var bar = root.querySelector('[data-bar]'), meter = root.querySelector('[role="progressbar"]');
      if (bar) bar.style.width = pct + '%';
      if (meter) { meter.setAttribute('aria-valuenow', Math.round(pct)); meter.setAttribute('aria-valuetext', out.pct + ', ' + left); }
    });
    setTimeout(tick, 1000 - Date.now() % 1000);
  }
  tick();
})();

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div><span class="lbl">Dashboard tiles</span>
<div class="tiles row row-cols-1 row-cols-sm-2 row-cols-md-3" data-widgets>
  <div class="col"><div class="card h-100"><div class="card-body"><h3 class="text-uppercase fw-bold">Local time</h3><b class="d-block fw-semibold" data-show="time">--:--</b><span class="note d-block" data-show="date">—</span></div></div></div>
  <div class="col"><div class="card h-100"><div class="card-body"><h3 class="text-uppercase fw-bold">Working day</h3><b class="d-block fw-semibold" data-show="pct">—</b>
    <div class="progress" role="progressbar" aria-label="Working day" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"><div class="progress-bar" data-bar style="width:0"></div></div>
    <span class="note d-block" data-show="left">—</span></div></div></div>
  <div class="col"><div class="card h-100"><div class="card-body"><h3 class="text-uppercase fw-bold">Status</h3><div class="stamp d-inline-flex align-items-center"><i class="d-block rounded-circle" aria-hidden="true"></i><span data-show="stamp">Updated just now</span></div></div></div></div>
</div></div>
CSS
/* Dashboard clock tiles on the Bootstrap grid: .card tiles and a .progress bar, restyled through --bs-card-* and
   --bs-progress-* variables. The script fills [data-show], sizes [data-bar] and updates the progressbar's aria values. */
.tiles{--bs-gutter-x:15px;--bs-gutter-y:15px}
.tiles .card{--bs-card-border-width:0;--bs-card-border-radius:13px;--bs-card-spacer-y:18px;--bs-card-spacer-x:20px;
  --bs-card-color:#1e293b;box-shadow:0 2px 10px rgba(15,23,42,.09)}
.tiles h3{font-size:.7rem;letter-spacing:.12em;color:#64748b;margin-bottom:11px}
.tiles b{font-size:1.75rem;line-height:1;font-variant-numeric:tabular-nums}
.tiles .note{font-size:.78rem;color:#64748b;margin-top:7px}
.tiles .progress{--bs-progress-height:7px;--bs-progress-bg:#e2e8f0;--bs-progress-bar-bg:#2563eb;--bs-progress-border-radius:99px;margin-top:13px}
.tiles .progress-bar{border-radius:99px;transition:width .5s}
.tiles .stamp{gap:8px;font-size:.82rem;color:#475569}
.tiles .stamp i{width:7px;height:7px;background:#16a34a}
@media (prefers-reduced-motion:reduce){.tiles .progress-bar{transition:none}}
JavaScript
// Dashboard clock tiles: local time and date, progress through the working day (START to END, local hours)
// and a "last updated" stamp. Re-timed to each second boundary; text only changes when its value does.
(function () {
  var roots = document.querySelectorAll('[data-widgets]');
  if (!roots.length) return;
  var START = 9, END = 18;
  function p2(n) { return String(n).padStart(2, '0'); }
  function set(el, text) { if (el && el.textContent !== text) el.textContent = text; }
  function tick() {
    var d = new Date(), H = d.getHours(), M = d.getMinutes();
    var mins = H * 60 + M, from = START * 60, to = END * 60;
    var pct = Math.max(0, Math.min(100, (mins - from) / (to - from) * 100));
    var left = pct <= 0 ? 'Not started' : pct >= 100 ? 'Finished for the day'
      : Math.floor((to - mins) / 60) + 'h ' + (to - mins) % 60 + 'm remaining';
    var out = {
      time: p2(H) + ':' + p2(M),
      date: d.toLocaleDateString(undefined, { weekday: 'long', day: 'numeric', month: 'short' }),
      pct: Math.round(pct) + '%', left: left,
      stamp: 'Updated ' + p2(H) + ':' + p2(M) + ':' + p2(d.getSeconds())
    };
    roots.forEach(function (root) {
      root.querySelectorAll('[data-show]').forEach(function (el) { set(el, out[el.getAttribute('data-show')]); });
      var bar = root.querySelector('[data-bar]'), meter = root.querySelector('[role="progressbar"]');
      if (bar) bar.style.width = pct + '%';
      if (meter) { meter.setAttribute('aria-valuenow', Math.round(pct)); meter.setAttribute('aria-valuetext', out.pct + ', ' + left); }
    });
    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">Dashboard tiles</span>
<div class="grid grid-cols-[repeat(auto-fill,minmax(215px,1fr))] gap-[15px]" data-widgets>
  <div class="rounded-[13px] bg-white px-5 py-[18px] shadow-[0_2px_10px_rgba(15,23,42,.09)]"><h3 class="mb-[11px] text-[.7rem] font-bold tracking-[.12em] text-[#64748b] uppercase">Local time</h3><b class="block text-[1.75rem] leading-none font-semibold tabular-nums" data-show="time">--:--</b><span class="mt-[7px] block text-[.78rem] text-[#64748b]" data-show="date">—</span></div>
  <div class="rounded-[13px] bg-white px-5 py-[18px] shadow-[0_2px_10px_rgba(15,23,42,.09)]"><h3 class="mb-[11px] text-[.7rem] font-bold tracking-[.12em] text-[#64748b] uppercase">Working day</h3><b class="block text-[1.75rem] leading-none font-semibold tabular-nums" data-show="pct">—</b>
    <div class="mt-[13px] h-[7px] overflow-hidden rounded-full bg-[#e2e8f0]" role="progressbar" aria-label="Working day" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"><i class="block h-full rounded-full bg-[#2563eb] transition-[width] duration-500 motion-reduce:transition-none" data-bar style="width:0"></i></div>
    <span class="mt-[7px] block text-[.78rem] text-[#64748b]" data-show="left">—</span></div>
  <div class="rounded-[13px] bg-white px-5 py-[18px] shadow-[0_2px_10px_rgba(15,23,42,.09)]"><h3 class="mb-[11px] text-[.7rem] font-bold tracking-[.12em] text-[#64748b] uppercase">Status</h3><div class="inline-flex items-center gap-2 text-[.82rem] text-[#475569]"><i class="block size-[7px] rounded-full bg-[#16a34a]" aria-hidden="true"></i><span data-show="stamp">Updated just now</span></div></div>
</div></div>
JavaScript
// Dashboard clock tiles: local time and date, progress through the working day (START to END, local hours)
// and a "last updated" stamp. Re-timed to each second boundary; text only changes when its value does.
(function () {
  var roots = document.querySelectorAll('[data-widgets]');
  if (!roots.length) return;
  var START = 9, END = 18;
  function p2(n) { return String(n).padStart(2, '0'); }
  function set(el, text) { if (el && el.textContent !== text) el.textContent = text; }
  function tick() {
    var d = new Date(), H = d.getHours(), M = d.getMinutes();
    var mins = H * 60 + M, from = START * 60, to = END * 60;
    var pct = Math.max(0, Math.min(100, (mins - from) / (to - from) * 100));
    var left = pct <= 0 ? 'Not started' : pct >= 100 ? 'Finished for the day'
      : Math.floor((to - mins) / 60) + 'h ' + (to - mins) % 60 + 'm remaining';
    var out = {
      time: p2(H) + ':' + p2(M),
      date: d.toLocaleDateString(undefined, { weekday: 'long', day: 'numeric', month: 'short' }),
      pct: Math.round(pct) + '%', left: left,
      stamp: 'Updated ' + p2(H) + ':' + p2(M) + ':' + p2(d.getSeconds())
    };
    roots.forEach(function (root) {
      root.querySelectorAll('[data-show]').forEach(function (el) { set(el, out[el.getAttribute('data-show')]); });
      var bar = root.querySelector('[data-bar]'), meter = root.querySelector('[role="progressbar"]');
      if (bar) bar.style.width = pct + '%';
      if (meter) { meter.setAttribute('aria-valuenow', Math.round(pct)); meter.setAttribute('aria-valuetext', out.pct + ', ' + left); }
    });
    setTimeout(tick, 1000 - Date.now() % 1000);
  }
  tick();
})();

About this set

Three small tiles sized for the corner of an admin dashboard. The local time tile shows hours and minutes with the weekday and date. The working day tile shows how far through the working day it is, as a percentage, a slim blue progress bar and a line such as 5h 30m remaining, Not started or Finished for the day. The status tile is a green dot and a last-updated stamp with seconds, the kind of line that sits under a data panel to show it is fresh. The working day runs from START to END local hours, 09:00 to 18:00 by default, set on one line in the script. The tiles sit in a grid that fills as many 215 pixel columns as fit, from one on a phone to three in an 860 pixel panel. The script fills each [data-show] element, sets the bar width and keeps the progress bar’s aria values in step once a second, and only touches text whose value changed. The Bootstrap version uses the grid, .card and .progress components restyled through their CSS variables, and the Tailwind version uses utility classes only.

What’s included

  • Local time and date tile
  • Working-day progress as a percentage, a bar and the time remaining
  • Last-updated status stamp with a green dot
  • Progress bar with role="progressbar" and live aria-valuenow
  • Working hours set by START and END in the script

Accessibility

The working-day bar has role="progressbar", an aria-label and aria-valuenow and aria-valuetext that follow the value, such as "39%, 5h 30m remaining". All other values are plain text, none of it in a live region, so screen readers are not interrupted. Tile headings and notes were darkened to 4.5:1, and the bar's width transition is removed under prefers-reduced-motion.

Customise it

Set START and END in the script. In plain CSS change the colours on .tile, .tile .bar and .tile .bar i; in Bootstrap the --bs-progress-* variables on .tiles .progress and the --bs-card-* variables on .tiles .card; in Tailwind the bg- and text- utilities on each tile and the bar.